HEX
Server: Apache
System: Linux vmi318001.contaboserver.net 6.8.0-117-generic #117-Ubuntu SMP PREEMPT_DYNAMIC Tue May 5 19:26:24 UTC 2026 x86_64
User: boxelikax (1004)
PHP: 8.2.33
Disabled: NONE
Upload Files
File: /home/boxelikax/public_html/demoltec.com/src.zip
PK�e]�<�ZZ(Operations/DTO/GenerativeAiOperation.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Operations\DTO;

use WordPress\AiClient\Common\AbstractDataTransferObject;
use WordPress\AiClient\Operations\Contracts\OperationInterface;
use WordPress\AiClient\Operations\Enums\OperationStateEnum;
use WordPress\AiClient\Results\DTO\GenerativeAiResult;
/**
 * Represents a long-running generative AI operation.
 *
 * This DTO tracks the progress of generative AI tasks that may not complete
 * immediately, providing access to the result once available.
 *
 * @since 0.1.0
 *
 * @phpstan-import-type GenerativeAiResultArrayShape from GenerativeAiResult
 *
 * @phpstan-type GenerativeAiOperationArrayShape array{id: string, state: string, result?: GenerativeAiResultArrayShape}
 *
 * @extends AbstractDataTransferObject<GenerativeAiOperationArrayShape>
 */
class GenerativeAiOperation extends AbstractDataTransferObject implements OperationInterface
{
    public const KEY_ID = 'id';
    public const KEY_STATE = 'state';
    public const KEY_RESULT = 'result';
    /**
     * @var string Unique identifier for this operation.
     */
    private string $id;
    /**
     * @var OperationStateEnum The current state of the operation.
     */
    private OperationStateEnum $state;
    /**
     * @var GenerativeAiResult|null The result once the operation completes.
     */
    private ?GenerativeAiResult $result;
    /**
     * Constructor.
     *
     * @since 0.1.0
     *
     * @param string $id Unique identifier for this operation.
     * @param OperationStateEnum $state The current state of the operation.
     * @param GenerativeAiResult|null $result The result once the operation completes.
     */
    public function __construct(string $id, OperationStateEnum $state, ?GenerativeAiResult $result = null)
    {
        $this->id = $id;
        $this->state = $state;
        $this->result = $result;
    }
    /**
     * Creates a deep clone of this operation.
     *
     * Clones the result object if present to ensure the cloned
     * operation is independent of the original.
     * The state enum is immutable and can be safely shared.
     *
     * @since 0.4.2
     */
    public function __clone()
    {
        // Clone the result if present (GenerativeAiResult has __clone)
        if ($this->result !== null) {
            $this->result = clone $this->result;
        }
        // Note: $state is an immutable enum and can be safely shared
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public function getId(): string
    {
        return $this->id;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public function getState(): OperationStateEnum
    {
        return $this->state;
    }
    /**
     * Gets the operation result.
     *
     * @since 0.1.0
     *
     * @return GenerativeAiResult|null The result or null if not yet complete.
     */
    public function getResult(): ?GenerativeAiResult
    {
        return $this->result;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function getJsonSchema(): array
    {
        return ['oneOf' => [
            // Succeeded state - has result
            ['type' => 'object', 'properties' => [self::KEY_ID => ['type' => 'string', 'description' => 'Unique identifier for this operation.'], self::KEY_STATE => ['type' => 'string', 'const' => OperationStateEnum::succeeded()->value], self::KEY_RESULT => GenerativeAiResult::getJsonSchema()], 'required' => [self::KEY_ID, self::KEY_STATE, self::KEY_RESULT], 'additionalProperties' => \false],
            // All other states - no result
            ['type' => 'object', 'properties' => [self::KEY_ID => ['type' => 'string', 'description' => 'Unique identifier for this operation.'], self::KEY_STATE => ['type' => 'string', 'enum' => [OperationStateEnum::starting()->value, OperationStateEnum::processing()->value, OperationStateEnum::failed()->value, OperationStateEnum::canceled()->value], 'description' => 'The current state of the operation.']], 'required' => [self::KEY_ID, self::KEY_STATE], 'additionalProperties' => \false],
        ]];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     *
     * @return GenerativeAiOperationArrayShape
     */
    public function toArray(): array
    {
        $data = [self::KEY_ID => $this->id, self::KEY_STATE => $this->state->value];
        if ($this->result !== null) {
            $data[self::KEY_RESULT] = $this->result->toArray();
        }
        return $data;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function fromArray(array $array): self
    {
        static::validateFromArrayData($array, [self::KEY_ID, self::KEY_STATE]);
        $state = OperationStateEnum::from($array[self::KEY_STATE]);
        if ($state->isSucceeded()) {
            // If the operation has succeeded, it must have a result
            static::validateFromArrayData($array, [self::KEY_RESULT]);
        }
        $result = null;
        if (isset($array[self::KEY_RESULT])) {
            $result = GenerativeAiResult::fromArray($array[self::KEY_RESULT]);
        }
        return new self($array[self::KEY_ID], $state, $result);
    }
}
PK�e]�8CC'Operations/Enums/OperationStateEnum.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Operations\Enums;

use WordPress\AiClient\Common\AbstractEnum;
/**
 * Enum for operation states.
 *
 * @since 0.1.0
 *
 * @method static self starting() Creates an instance for STARTING state.
 * @method static self processing() Creates an instance for PROCESSING state.
 * @method static self succeeded() Creates an instance for SUCCEEDED state.
 * @method static self failed() Creates an instance for FAILED state.
 * @method static self canceled() Creates an instance for CANCELED state.
 * @method bool isStarting() Checks if the state is STARTING.
 * @method bool isProcessing() Checks if the state is PROCESSING.
 * @method bool isSucceeded() Checks if the state is SUCCEEDED.
 * @method bool isFailed() Checks if the state is FAILED.
 * @method bool isCanceled() Checks if the state is CANCELED.
 */
class OperationStateEnum extends AbstractEnum
{
    /**
     * Operation is starting.
     */
    public const STARTING = 'starting';
    /**
     * Operation is processing.
     */
    public const PROCESSING = 'processing';
    /**
     * Operation succeeded.
     */
    public const SUCCEEDED = 'succeeded';
    /**
     * Operation failed.
     */
    public const FAILED = 'failed';
    /**
     * Operation was canceled.
     */
    public const CANCELED = 'canceled';
}
PK�e]�I�+Operations/Contracts/OperationInterface.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Operations\Contracts;

use WordPress\AiClient\Operations\Enums\OperationStateEnum;
/**
 * Interface for AI operations.
 *
 * Operations represent long-running AI tasks that may not complete immediately.
 * They provide a way to track the progress and retrieve results asynchronously.
 *
 * @since 0.1.0
 */
interface OperationInterface
{
    /**
     * Gets the operation ID.
     *
     * @since 0.1.0
     *
     * @return string The unique operation identifier.
     */
    public function getId(): string;
    /**
     * Gets the current state of the operation.
     *
     * @since 0.1.0
     *
     * @return OperationStateEnum The operation state.
     */
    public function getState(): OperationStateEnum;
}
PK�e](cBC�C�Builders/PromptBuilder.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Builders;

use WordPress\AiClientDependencies\Psr\EventDispatcher\EventDispatcherInterface;
use WordPress\AiClient\Common\Exception\InvalidArgumentException;
use WordPress\AiClient\Common\Exception\RuntimeException;
use WordPress\AiClient\Events\AfterGenerateResultEvent;
use WordPress\AiClient\Events\BeforeGenerateResultEvent;
use WordPress\AiClient\Files\DTO\File;
use WordPress\AiClient\Files\Enums\FileTypeEnum;
use WordPress\AiClient\Files\Enums\MediaOrientationEnum;
use WordPress\AiClient\Messages\DTO\Message;
use WordPress\AiClient\Messages\DTO\MessagePart;
use WordPress\AiClient\Messages\DTO\UserMessage;
use WordPress\AiClient\Messages\Enums\MessageRoleEnum;
use WordPress\AiClient\Messages\Enums\ModalityEnum;
use WordPress\AiClient\Providers\ApiBasedImplementation\Contracts\ApiBasedModelInterface;
use WordPress\AiClient\Providers\Http\DTO\RequestOptions;
use WordPress\AiClient\Providers\Models\Contracts\ModelInterface;
use WordPress\AiClient\Providers\Models\DTO\ModelConfig;
use WordPress\AiClient\Providers\Models\DTO\ModelMetadata;
use WordPress\AiClient\Providers\Models\DTO\ModelRequirements;
use WordPress\AiClient\Providers\Models\Enums\CapabilityEnum;
use WordPress\AiClient\Providers\Models\ImageGeneration\Contracts\ImageGenerationModelInterface;
use WordPress\AiClient\Providers\Models\SpeechGeneration\Contracts\SpeechGenerationModelInterface;
use WordPress\AiClient\Providers\Models\TextGeneration\Contracts\TextGenerationModelInterface;
use WordPress\AiClient\Providers\Models\TextToSpeechConversion\Contracts\TextToSpeechConversionModelInterface;
use WordPress\AiClient\Providers\Models\VideoGeneration\Contracts\VideoGenerationModelInterface;
use WordPress\AiClient\Providers\ProviderRegistry;
use WordPress\AiClient\Results\DTO\GenerativeAiResult;
use WordPress\AiClient\Tools\DTO\FunctionDeclaration;
use WordPress\AiClient\Tools\DTO\FunctionResponse;
use WordPress\AiClient\Tools\DTO\WebSearch;
/**
 * Fluent builder for constructing AI prompts.
 *
 * This class provides a fluent interface for building prompts with various
 * content types and model configurations. It automatically infers model
 * requirements based on the features used in the prompt.
 *
 * @since 0.1.0
 *
 * @phpstan-import-type MessageArrayShape from Message
 * @phpstan-import-type MessagePartArrayShape from MessagePart
 *
 * @phpstan-type Prompt string|MessagePart|Message|MessageArrayShape|list<string|MessagePart|MessagePartArrayShape>|list<Message>|null
 */
class PromptBuilder
{
    /**
     * @var ProviderRegistry The provider registry for finding suitable models.
     */
    private ProviderRegistry $registry;
    /**
     * @var list<Message> The messages in the conversation.
     */
    protected array $messages = [];
    /**
     * @var ModelInterface|null The model to use for generation.
     */
    protected ?ModelInterface $model = null;
    /**
     * @var list<string> Ordered list of preference keys to check when selecting a model.
     */
    protected array $modelPreferenceKeys = [];
    /**
     * @var string|null The provider ID or class name.
     */
    protected ?string $providerIdOrClassName = null;
    /**
     * @var ModelConfig The model configuration.
     */
    protected ModelConfig $modelConfig;
    /**
     * @var RequestOptions|null The request options for HTTP transport.
     */
    protected ?RequestOptions $requestOptions = null;
    /**
     * @var EventDispatcherInterface|null The event dispatcher for prompt lifecycle events.
     */
    private ?EventDispatcherInterface $eventDispatcher = null;
    // phpcs:disable Generic.Files.LineLength.TooLong
    /**
     * Constructor.
     *
     * @since 0.1.0
     *
     * @param ProviderRegistry $registry The provider registry for finding suitable models.
     * @param Prompt $prompt Optional initial prompt content.
     * @param EventDispatcherInterface|null $eventDispatcher Optional event dispatcher for lifecycle events.
     */
    // phpcs:enable Generic.Files.LineLength.TooLong
    public function __construct(ProviderRegistry $registry, $prompt = null, ?EventDispatcherInterface $eventDispatcher = null)
    {
        $this->registry = $registry;
        $this->modelConfig = new ModelConfig();
        $this->eventDispatcher = $eventDispatcher;
        if ($prompt === null) {
            return;
        }
        // Check if it's a list of Messages - set as messages
        if ($this->isMessagesList($prompt)) {
            $this->messages = $prompt;
            return;
        }
        // Parse it as a user message
        $userMessage = $this->parseMessage($prompt, MessageRoleEnum::user());
        $this->messages[] = $userMessage;
    }
    /**
     * Creates a deep clone of this builder.
     *
     * Clones all mutable state including messages, model configuration, and request options.
     * Service objects (registry, model, event dispatcher) are intentionally NOT cloned
     * as they are shared dependencies.
     *
     * @since 0.4.2
     */
    public function __clone()
    {
        // Deep clone messages array (Message has __clone)
        $clonedMessages = [];
        foreach ($this->messages as $message) {
            $clonedMessages[] = clone $message;
        }
        $this->messages = $clonedMessages;
        // Clone model config (ModelConfig has __clone)
        $this->modelConfig = clone $this->modelConfig;
        // Clone request options if set (contains only primitives)
        if ($this->requestOptions !== null) {
            $this->requestOptions = clone $this->requestOptions;
        }
        // Note: $registry, $model, and $eventDispatcher are service objects
        // and are intentionally NOT cloned - they should be shared references.
    }
    /**
     * Adds text to the current message.
     *
     * @since 0.1.0
     *
     * @param string $text The text to add.
     * @return self
     */
    public function withText(string $text): self
    {
        $part = new MessagePart($text);
        $this->appendPartToMessages($part);
        return $this;
    }
    /**
     * Adds a file to the current message.
     *
     * Accepts:
     * - File object
     * - URL string (remote file)
     * - Base64-encoded data string
     * - Data URI string (data:mime/type;base64,data)
     * - Local file path string
     *
     * @since 0.1.0
     *
     * @param string|File $file The file (File object or string representation).
     * @param string|null $mimeType The MIME type (optional, ignored if File object provided).
     * @return self
     * @throws InvalidArgumentException If the file is invalid or MIME type cannot be determined.
     */
    public function withFile($file, ?string $mimeType = null): self
    {
        $file = $file instanceof File ? $file : new File($file, $mimeType);
        $part = new MessagePart($file);
        $this->appendPartToMessages($part);
        return $this;
    }
    /**
     * Adds a function response to the current message.
     *
     * @since 0.1.0
     *
     * @param FunctionResponse $functionResponse The function response.
     * @return self
     */
    public function withFunctionResponse(FunctionResponse $functionResponse): self
    {
        $part = new MessagePart($functionResponse);
        $this->appendPartToMessages($part);
        return $this;
    }
    /**
     * Adds message parts to the current message.
     *
     * @since 0.1.0
     *
     * @param MessagePart ...$parts The message parts to add.
     * @return self
     */
    public function withMessageParts(MessagePart ...$parts): self
    {
        foreach ($parts as $part) {
            $this->appendPartToMessages($part);
        }
        return $this;
    }
    /**
     * Adds conversation history messages.
     *
     * Historical messages are prepended to the beginning of the message list,
     * before the current message being built.
     *
     * @since 0.1.0
     *
     * @param Message ...$messages The messages to add to history.
     * @return self
     */
    public function withHistory(Message ...$messages): self
    {
        // Prepend the history messages to the beginning of the messages array
        $this->messages = array_merge($messages, $this->messages);
        return $this;
    }
    /**
     * Sets the model to use for generation.
     *
     * The model's configuration will be merged with the builder's configuration,
     * with the builder's configuration taking precedence for any overlapping settings.
     *
     * @since 0.1.0
     *
     * @param ModelInterface $model The model to use.
     * @return self
     */
    public function usingModel(ModelInterface $model): self
    {
        $this->model = $model;
        // Merge model's config with builder's config, with builder's config taking precedence
        $modelConfigArray = $model->getConfig()->toArray();
        $builderConfigArray = $this->modelConfig->toArray();
        $mergedConfigArray = array_merge($modelConfigArray, $builderConfigArray);
        $this->modelConfig = ModelConfig::fromArray($mergedConfigArray);
        return $this;
    }
    /**
     * Sets preferred models to evaluate in order.
     *
     * @since 0.2.0
     *
     * @param string|ModelInterface|array{0:string,1:string} ...$preferredModels The preferred models as model IDs,
     * model instances, or [provider ID, model ID] tuples. For broader compatibility, it is recommended you specify
     * only model IDs or model instances, as that will allow for different providers that expose the same model to be
     * considered.
     * @return self
     *
     * @throws InvalidArgumentException When a preferred model has an invalid type or identifier.
     */
    public function usingModelPreference(...$preferredModels): self
    {
        if ($preferredModels === []) {
            throw new InvalidArgumentException('At least one model preference must be provided.');
        }
        $preferenceKeys = [];
        foreach ($preferredModels as $preferredModel) {
            if (is_array($preferredModel)) {
                // [model identifier, provider ID] tuple
                if (!array_is_list($preferredModel) || count($preferredModel) !== 2) {
                    throw new InvalidArgumentException('Model preference tuple must contain model identifier and provider ID.');
                }
                [$providerId, $modelId] = $preferredModel;
                $modelId = $this->normalizePreferenceIdentifier($modelId);
                $providerId = $this->normalizePreferenceIdentifier($providerId, 'Model preference provider identifiers cannot be empty.');
                $preferenceKey = $this->createProviderModelPreferenceKey($providerId, $modelId);
            } elseif ($preferredModel instanceof ModelInterface) {
                // Model instance
                $modelId = $preferredModel->metadata()->getId();
                $providerId = $preferredModel->providerMetadata()->getId();
                $preferenceKey = $this->createProviderModelPreferenceKey($providerId, $modelId);
            } elseif (is_string($preferredModel)) {
                // Model ID
                $modelId = $this->normalizePreferenceIdentifier($preferredModel);
                $preferenceKey = $this->createModelPreferenceKey($modelId);
            } else {
                // Invalid type
                throw new InvalidArgumentException('Model preferences must be model identifiers, instances of ModelInterface, ' . 'or provider/model tuples.');
            }
            $preferenceKeys[] = $preferenceKey;
        }
        $this->modelPreferenceKeys = $preferenceKeys;
        return $this;
    }
    /**
     * Sets the model configuration.
     *
     * Merges the provided configuration with the builder's configuration,
     * with builder configuration taking precedence.
     *
     * @since 0.1.0
     *
     * @param ModelConfig $config The model configuration to merge.
     * @return self
     */
    public function usingModelConfig(ModelConfig $config): self
    {
        // Convert both configs to arrays
        $builderConfigArray = $this->modelConfig->toArray();
        $providedConfigArray = $config->toArray();
        // Merge arrays with builder config taking precedence
        $mergedArray = array_merge($providedConfigArray, $builderConfigArray);
        // Create new config from merged array
        $this->modelConfig = ModelConfig::fromArray($mergedArray);
        return $this;
    }
    /**
     * Sets the provider to use for generation.
     *
     * @since 0.1.0
     *
     * @param string $providerIdOrClassName The provider ID or class name.
     * @return self
     */
    public function usingProvider(string $providerIdOrClassName): self
    {
        $this->providerIdOrClassName = $providerIdOrClassName;
        return $this;
    }
    /**
     * Sets the system instruction.
     *
     * System instructions are stored in the model configuration and guide
     * the AI model's behavior throughout the conversation.
     *
     * @since 0.1.0
     *
     * @param string $systemInstruction The system instruction text.
     * @return self
     */
    public function usingSystemInstruction(string $systemInstruction): self
    {
        $this->modelConfig->setSystemInstruction($systemInstruction);
        return $this;
    }
    /**
     * Sets the maximum number of tokens to generate.
     *
     * @since 0.1.0
     *
     * @param int $maxTokens The maximum number of tokens.
     * @return self
     */
    public function usingMaxTokens(int $maxTokens): self
    {
        $this->modelConfig->setMaxTokens($maxTokens);
        return $this;
    }
    /**
     * Sets the temperature for generation.
     *
     * @since 0.1.0
     *
     * @param float $temperature The temperature value.
     * @return self
     */
    public function usingTemperature(float $temperature): self
    {
        $this->modelConfig->setTemperature($temperature);
        return $this;
    }
    /**
     * Sets the top-p value for generation.
     *
     * @since 0.1.0
     *
     * @param float $topP The top-p value.
     * @return self
     */
    public function usingTopP(float $topP): self
    {
        $this->modelConfig->setTopP($topP);
        return $this;
    }
    /**
     * Sets the top-k value for generation.
     *
     * @since 0.1.0
     *
     * @param int $topK The top-k value.
     * @return self
     */
    public function usingTopK(int $topK): self
    {
        $this->modelConfig->setTopK($topK);
        return $this;
    }
    /**
     * Sets stop sequences for generation.
     *
     * @since 0.1.0
     *
     * @param string ...$stopSequences The stop sequences.
     * @return self
     */
    public function usingStopSequences(string ...$stopSequences): self
    {
        $this->modelConfig->setStopSequences($stopSequences);
        return $this;
    }
    /**
     * Sets the number of candidates to generate.
     *
     * @since 0.1.0
     *
     * @param int $candidateCount The number of candidates.
     * @return self
     */
    public function usingCandidateCount(int $candidateCount): self
    {
        $this->modelConfig->setCandidateCount($candidateCount);
        return $this;
    }
    /**
     * Sets the function declarations available to the model.
     *
     * @since 0.1.0
     *
     * @param FunctionDeclaration ...$functionDeclarations The function declarations.
     * @return self
     */
    public function usingFunctionDeclarations(FunctionDeclaration ...$functionDeclarations): self
    {
        $this->modelConfig->setFunctionDeclarations($functionDeclarations);
        return $this;
    }
    /**
     * Sets the presence penalty for generation.
     *
     * @since 0.1.0
     *
     * @param float $presencePenalty The presence penalty value.
     * @return self
     */
    public function usingPresencePenalty(float $presencePenalty): self
    {
        $this->modelConfig->setPresencePenalty($presencePenalty);
        return $this;
    }
    /**
     * Sets the frequency penalty for generation.
     *
     * @since 0.1.0
     *
     * @param float $frequencyPenalty The frequency penalty value.
     * @return self
     */
    public function usingFrequencyPenalty(float $frequencyPenalty): self
    {
        $this->modelConfig->setFrequencyPenalty($frequencyPenalty);
        return $this;
    }
    /**
     * Sets the web search configuration.
     *
     * @since 0.1.0
     *
     * @param WebSearch $webSearch The web search configuration.
     * @return self
     */
    public function usingWebSearch(WebSearch $webSearch): self
    {
        $this->modelConfig->setWebSearch($webSearch);
        return $this;
    }
    /**
     * Sets the request options for HTTP transport.
     *
     * @since 0.3.0
     *
     * @param RequestOptions $requestOptions The request options.
     * @return self
     */
    public function usingRequestOptions(RequestOptions $requestOptions): self
    {
        $this->requestOptions = $requestOptions;
        return $this;
    }
    /**
     * Sets the top log probabilities configuration.
     *
     * If $topLogprobs is null, enables log probabilities.
     * If $topLogprobs has a value, enables log probabilities and sets the number of top log probabilities to return.
     *
     * @since 0.1.0
     *
     * @param int|null $topLogprobs The number of top log probabilities to return, or null to enable log probabilities.
     * @return self
     */
    public function usingTopLogprobs(?int $topLogprobs = null): self
    {
        // Always enable log probabilities
        $this->modelConfig->setLogprobs(\true);
        // If a specific number is provided, set it
        if ($topLogprobs !== null) {
            $this->modelConfig->setTopLogprobs($topLogprobs);
        }
        return $this;
    }
    /**
     * Sets the output MIME type.
     *
     * @since 0.1.0
     *
     * @param string $mimeType The MIME type.
     * @return self
     */
    public function asOutputMimeType(string $mimeType): self
    {
        $this->modelConfig->setOutputMimeType($mimeType);
        return $this;
    }
    /**
     * Sets the output schema.
     *
     * @since 0.1.0
     *
     * @param array<string, mixed> $schema The output schema.
     * @return self
     */
    public function asOutputSchema(array $schema): self
    {
        $this->modelConfig->setOutputSchema($schema);
        return $this;
    }
    /**
     * Sets the output modalities.
     *
     * @since 0.1.0
     *
     * @param ModalityEnum ...$modalities The output modalities.
     * @return self
     */
    public function asOutputModalities(ModalityEnum ...$modalities): self
    {
        $this->modelConfig->setOutputModalities($modalities);
        return $this;
    }
    /**
     * Sets the output file type.
     *
     * @since 0.1.0
     *
     * @param FileTypeEnum $fileType The output file type.
     * @return self
     */
    public function asOutputFileType(FileTypeEnum $fileType): self
    {
        $this->modelConfig->setOutputFileType($fileType);
        return $this;
    }
    /**
     * Sets the output media orientation.
     *
     * @since 1.3.0
     *
     * @param MediaOrientationEnum $orientation The output media orientation.
     * @return self
     */
    public function asOutputMediaOrientation(MediaOrientationEnum $orientation): self
    {
        $this->modelConfig->setOutputMediaOrientation($orientation);
        return $this;
    }
    /**
     * Sets the output media aspect ratio.
     *
     * If set, this supersedes the output media orientation, as it is a more
     * specific configuration.
     *
     * @since 1.3.0
     *
     * @param string $aspectRatio The aspect ratio (e.g. "16:9", "3:2").
     * @return self
     */
    public function asOutputMediaAspectRatio(string $aspectRatio): self
    {
        $this->modelConfig->setOutputMediaAspectRatio($aspectRatio);
        return $this;
    }
    /**
     * Sets the output speech voice.
     *
     * @since 1.3.0
     *
     * @param string $voice The output speech voice.
     * @return self
     */
    public function asOutputSpeechVoice(string $voice): self
    {
        $this->modelConfig->setOutputSpeechVoice($voice);
        return $this;
    }
    /**
     * Configures the prompt for JSON response output.
     *
     * @since 0.1.0
     *
     * @param array<string, mixed>|null $schema Optional JSON schema.
     * @return self
     */
    public function asJsonResponse(?array $schema = null): self
    {
        $this->asOutputMimeType('application/json');
        if ($schema !== null) {
            $this->asOutputSchema($schema);
        }
        return $this;
    }
    /**
     * Infers the capability from configured output modalities.
     *
     * @since 0.1.0
     *
     * @return CapabilityEnum The inferred capability.
     * @throws RuntimeException If the output modality is not supported.
     */
    private function inferCapabilityFromOutputModalities(): CapabilityEnum
    {
        // Get the configured output modalities
        $outputModalities = $this->modelConfig->getOutputModalities();
        // Default to text if no output modality is specified
        if ($outputModalities === null || empty($outputModalities)) {
            return CapabilityEnum::textGeneration();
        }
        // Multi-modal output (multiple modalities) defaults to text generation. This is temporary
        // as a multi-modal interface will be implemented in the future.
        if (count($outputModalities) > 1) {
            return CapabilityEnum::textGeneration();
        }
        // Infer capability from single output modality
        $outputModality = $outputModalities[0];
        if ($outputModality->isText()) {
            return CapabilityEnum::textGeneration();
        } elseif ($outputModality->isImage()) {
            return CapabilityEnum::imageGeneration();
        } elseif ($outputModality->isAudio()) {
            return CapabilityEnum::speechGeneration();
        } elseif ($outputModality->isVideo()) {
            return CapabilityEnum::videoGeneration();
        } else {
            // For unsupported modalities, provide a clear error message
            throw new RuntimeException(sprintf('Output modality "%s" is not yet supported.', $outputModality->value));
        }
    }
    /**
     * Infers the capability from a model's implemented interfaces.
     *
     * @since 0.1.0
     *
     * @param ModelInterface $model The model to infer capability from.
     * @return CapabilityEnum|null The inferred capability, or null if none can be inferred.
     */
    private function inferCapabilityFromModelInterfaces(ModelInterface $model): ?CapabilityEnum
    {
        // Check model interfaces in order of preference
        if ($model instanceof TextGenerationModelInterface) {
            return CapabilityEnum::textGeneration();
        }
        if ($model instanceof ImageGenerationModelInterface) {
            return CapabilityEnum::imageGeneration();
        }
        if ($model instanceof TextToSpeechConversionModelInterface) {
            return CapabilityEnum::textToSpeechConversion();
        }
        if ($model instanceof SpeechGenerationModelInterface) {
            return CapabilityEnum::speechGeneration();
        }
        if ($model instanceof VideoGenerationModelInterface) {
            return CapabilityEnum::videoGeneration();
        }
        // No supported interface found
        return null;
    }
    /**
     * Checks if the current prompt is supported by the selected model.
     *
     * @since 0.1.0
     * @since 0.3.0 Method visibility changed to public.
     *
     * @param CapabilityEnum|null $capability Optional capability to check support for.
     * @return bool True if supported, false otherwise.
     */
    public function isSupported(?CapabilityEnum $capability = null): bool
    {
        // If no intended capability provided, infer from output modalities
        if ($capability === null) {
            // First try to infer from a specific model if one is set
            if ($this->model !== null) {
                $inferredCapability = $this->inferCapabilityFromModelInterfaces($this->model);
                if ($inferredCapability !== null) {
                    $capability = $inferredCapability;
                }
            }
            // If still no capability, infer from output modalities
            if ($capability === null) {
                $capability = $this->inferCapabilityFromOutputModalities();
            }
        }
        // Build requirements with the specified capability
        $requirements = ModelRequirements::fromPromptData($capability, $this->messages, $this->modelConfig);
        // If the model has been set, check if it meets the requirements
        if ($this->model !== null) {
            return $requirements->areMetBy($this->model->metadata());
        }
        try {
            // Check if any models support these requirements
            $models = $this->registry->findModelsMetadataForSupport($requirements);
            return !empty($models);
        } catch (InvalidArgumentException $e) {
            // No models support the requirements
            return \false;
        }
    }
    /**
     * Checks if the prompt is supported for text generation.
     *
     * @since 0.1.0
     *
     * @return bool True if text generation is supported.
     */
    public function isSupportedForTextGeneration(): bool
    {
        return $this->isSupported(CapabilityEnum::textGeneration());
    }
    /**
     * Checks if the prompt is supported for image generation.
     *
     * @since 0.1.0
     *
     * @return bool True if image generation is supported.
     */
    public function isSupportedForImageGeneration(): bool
    {
        return $this->isSupported(CapabilityEnum::imageGeneration());
    }
    /**
     * Checks if the prompt is supported for text to speech conversion.
     *
     * @since 0.1.0
     *
     * @return bool True if text to speech conversion is supported.
     */
    public function isSupportedForTextToSpeechConversion(): bool
    {
        return $this->isSupported(CapabilityEnum::textToSpeechConversion());
    }
    /**
     * Checks if the prompt is supported for video generation.
     *
     * @since 0.1.0
     *
     * @return bool True if video generation is supported.
     */
    public function isSupportedForVideoGeneration(): bool
    {
        return $this->isSupported(CapabilityEnum::videoGeneration());
    }
    /**
     * Checks if the prompt is supported for speech generation.
     *
     * @since 0.1.0
     *
     * @return bool True if speech generation is supported.
     */
    public function isSupportedForSpeechGeneration(): bool
    {
        return $this->isSupported(CapabilityEnum::speechGeneration());
    }
    /**
     * Checks if the prompt is supported for music generation.
     *
     * @since 0.1.0
     *
     * @return bool True if music generation is supported.
     */
    public function isSupportedForMusicGeneration(): bool
    {
        return $this->isSupported(CapabilityEnum::musicGeneration());
    }
    /**
     * Checks if the prompt is supported for embedding generation.
     *
     * @since 0.1.0
     *
     * @return bool True if embedding generation is supported.
     */
    public function isSupportedForEmbeddingGeneration(): bool
    {
        return $this->isSupported(CapabilityEnum::embeddingGeneration());
    }
    /**
     * Generates a result from the prompt.
     *
     * This is the primary execution method that generates a result (containing
     * potentially multiple candidates) based on the specified capability or
     * the configured output modality.
     *
     * @since 0.1.0
     *
     * @param CapabilityEnum|null $capability Optional capability to use for generation.
     *                                        If null, capability is inferred from output modality.
     * @return GenerativeAiResult The generated result containing candidates.
     * @throws InvalidArgumentException If the prompt or model validation fails.
     * @throws RuntimeException If the model doesn't support the required capability.
     */
    public function generateResult(?CapabilityEnum $capability = null): GenerativeAiResult
    {
        $this->validateMessages();
        // If capability is not provided, infer it
        if ($capability === null) {
            // First try to infer from a specific model if one is set
            if ($this->model !== null) {
                $inferredCapability = $this->inferCapabilityFromModelInterfaces($this->model);
                if ($inferredCapability !== null) {
                    $capability = $inferredCapability;
                }
            }
            // If still no capability, infer from output modalities
            if ($capability === null) {
                $capability = $this->inferCapabilityFromOutputModalities();
            }
        }
        $model = $this->getConfiguredModel($capability);
        // Dispatch BeforeGenerateResultEvent
        $this->dispatchEvent(new BeforeGenerateResultEvent($this->messages, $model, $capability));
        // Route to the appropriate generation method based on capability
        $result = $this->executeModelGeneration($model, $capability, $this->messages);
        // Dispatch AfterGenerateResultEvent
        $this->dispatchEvent(new AfterGenerateResultEvent($this->messages, $model, $capability, $result));
        return $result;
    }
    /**
     * Executes the model generation based on capability.
     *
     * @since 0.4.0
     *
     * @param ModelInterface $model The model to use for generation.
     * @param CapabilityEnum $capability The capability to use.
     * @param list<Message> $messages The messages to send.
     * @return GenerativeAiResult The generated result.
     * @throws RuntimeException If the model doesn't support the required capability.
     */
    private function executeModelGeneration(ModelInterface $model, CapabilityEnum $capability, array $messages): GenerativeAiResult
    {
        if ($capability->isTextGeneration()) {
            if (!$model instanceof TextGenerationModelInterface) {
                throw new RuntimeException(sprintf('Model "%s" does not support text generation.', $model->metadata()->getId()));
            }
            return $model->generateTextResult($messages);
        }
        if ($capability->isImageGeneration()) {
            if (!$model instanceof ImageGenerationModelInterface) {
                throw new RuntimeException(sprintf('Model "%s" does not support image generation.', $model->metadata()->getId()));
            }
            return $model->generateImageResult($messages);
        }
        if ($capability->isTextToSpeechConversion()) {
            if (!$model instanceof TextToSpeechConversionModelInterface) {
                throw new RuntimeException(sprintf('Model "%s" does not support text-to-speech conversion.', $model->metadata()->getId()));
            }
            return $model->convertTextToSpeechResult($messages);
        }
        if ($capability->isSpeechGeneration()) {
            if (!$model instanceof SpeechGenerationModelInterface) {
                throw new RuntimeException(sprintf('Model "%s" does not support speech generation.', $model->metadata()->getId()));
            }
            return $model->generateSpeechResult($messages);
        }
        if ($capability->isVideoGeneration()) {
            if (!$model instanceof VideoGenerationModelInterface) {
                throw new RuntimeException(sprintf('Model "%s" does not support video generation.', $model->metadata()->getId()));
            }
            return $model->generateVideoResult($messages);
        }
        // TODO: Add support for other capabilities when interfaces are available
        throw new RuntimeException(sprintf('Capability "%s" is not yet supported for generation.', $capability->value));
    }
    /**
     * Generates a text result from the prompt.
     *
     * @since 0.1.0
     *
     * @return GenerativeAiResult The generated result containing text candidates.
     * @throws InvalidArgumentException If the prompt or model validation fails.
     * @throws RuntimeException If the model doesn't support text generation.
     */
    public function generateTextResult(): GenerativeAiResult
    {
        // Include text in output modalities
        $this->includeOutputModalities(ModalityEnum::text());
        // Generate and return the result with text generation capability
        return $this->generateResult(CapabilityEnum::textGeneration());
    }
    /**
     * Generates an image result from the prompt.
     *
     * @since 0.1.0
     *
     * @return GenerativeAiResult The generated result containing image candidates.
     * @throws InvalidArgumentException If the prompt or model validation fails.
     * @throws RuntimeException If the model doesn't support image generation.
     */
    public function generateImageResult(): GenerativeAiResult
    {
        // Include image in output modalities
        $this->includeOutputModalities(ModalityEnum::image());
        // Generate and return the result with image generation capability
        return $this->generateResult(CapabilityEnum::imageGeneration());
    }
    /**
     * Generates a speech result from the prompt.
     *
     * @since 0.1.0
     *
     * @return GenerativeAiResult The generated result containing speech audio candidates.
     * @throws InvalidArgumentException If the prompt or model validation fails.
     * @throws RuntimeException If the model doesn't support speech generation.
     */
    public function generateSpeechResult(): GenerativeAiResult
    {
        // Include audio in output modalities
        $this->includeOutputModalities(ModalityEnum::audio());
        // Generate and return the result with speech generation capability
        return $this->generateResult(CapabilityEnum::speechGeneration());
    }
    /**
     * Converts text to speech and returns the result.
     *
     * @since 0.1.0
     *
     * @return GenerativeAiResult The generated result containing speech audio candidates.
     * @throws InvalidArgumentException If the prompt or model validation fails.
     * @throws RuntimeException If the model doesn't support text-to-speech conversion.
     */
    public function convertTextToSpeechResult(): GenerativeAiResult
    {
        // Include audio in output modalities
        $this->includeOutputModalities(ModalityEnum::audio());
        // Generate and return the result with text-to-speech conversion capability
        return $this->generateResult(CapabilityEnum::textToSpeechConversion());
    }
    /**
     * Generates a video result from the prompt.
     *
     * @since 1.3.0
     *
     * @return GenerativeAiResult The generated result containing video candidates.
     * @throws InvalidArgumentException If the prompt or model validation fails.
     * @throws RuntimeException If the model doesn't support video generation.
     */
    public function generateVideoResult(): GenerativeAiResult
    {
        // Include video in output modalities
        $this->includeOutputModalities(ModalityEnum::video());
        // Generate and return the result with video generation capability
        return $this->generateResult(CapabilityEnum::videoGeneration());
    }
    /**
     * Generates text from the prompt.
     *
     * @since 0.1.0
     *
     * @return string The generated text.
     * @throws InvalidArgumentException If the prompt or model validation fails.
     */
    public function generateText(): string
    {
        return $this->generateTextResult()->toText();
    }
    /**
     * Generates multiple text candidates from the prompt.
     *
     * @since 0.1.0
     *
     * @param int|null $candidateCount The number of candidates to generate.
     * @return list<string> The generated texts.
     * @throws InvalidArgumentException If the prompt or model validation fails.
     */
    public function generateTexts(?int $candidateCount = null): array
    {
        if ($candidateCount !== null) {
            $this->usingCandidateCount($candidateCount);
        }
        // Generate text result
        return $this->generateTextResult()->toTexts();
    }
    /**
     * Generates an image from the prompt.
     *
     * @since 0.1.0
     *
     * @return File The generated image file.
     * @throws InvalidArgumentException If the prompt or model validation fails.
     * @throws RuntimeException If no image is generated.
     */
    public function generateImage(): File
    {
        return $this->generateImageResult()->toFile();
    }
    /**
     * Generates multiple images from the prompt.
     *
     * @since 0.1.0
     *
     * @param int|null $candidateCount The number of images to generate.
     * @return list<File> The generated image files.
     * @throws InvalidArgumentException If the prompt or model validation fails.
     * @throws RuntimeException If no images are generated.
     */
    public function generateImages(?int $candidateCount = null): array
    {
        if ($candidateCount !== null) {
            $this->usingCandidateCount($candidateCount);
        }
        return $this->generateImageResult()->toFiles();
    }
    /**
     * Converts text to speech.
     *
     * @since 0.1.0
     *
     * @return File The generated speech audio file.
     * @throws InvalidArgumentException If the prompt or model validation fails.
     * @throws RuntimeException If no audio is generated.
     */
    public function convertTextToSpeech(): File
    {
        return $this->convertTextToSpeechResult()->toFile();
    }
    /**
     * Converts text to multiple speech outputs.
     *
     * @since 0.1.0
     *
     * @param int|null $candidateCount The number of speech outputs to generate.
     * @return list<File> The generated speech audio files.
     * @throws InvalidArgumentException If the prompt or model validation fails.
     * @throws RuntimeException If no audio is generated.
     */
    public function convertTextToSpeeches(?int $candidateCount = null): array
    {
        if ($candidateCount !== null) {
            $this->usingCandidateCount($candidateCount);
        }
        return $this->convertTextToSpeechResult()->toFiles();
    }
    /**
     * Generates speech from the prompt.
     *
     * @since 0.1.0
     *
     * @return File The generated speech audio file.
     * @throws InvalidArgumentException If the prompt or model validation fails.
     * @throws RuntimeException If no audio is generated.
     */
    public function generateSpeech(): File
    {
        return $this->generateSpeechResult()->toFile();
    }
    /**
     * Generates multiple speech outputs from the prompt.
     *
     * @since 0.1.0
     *
     * @param int|null $candidateCount The number of speech outputs to generate.
     * @return list<File> The generated speech audio files.
     * @throws InvalidArgumentException If the prompt or model validation fails.
     * @throws RuntimeException If no audio is generated.
     */
    public function generateSpeeches(?int $candidateCount = null): array
    {
        if ($candidateCount !== null) {
            $this->usingCandidateCount($candidateCount);
        }
        return $this->generateSpeechResult()->toFiles();
    }
    /**
     * Generates a video from the prompt.
     *
     * @since 1.3.0
     *
     * @return File The generated video file.
     * @throws InvalidArgumentException If the prompt or model validation fails.
     * @throws RuntimeException If no video is generated.
     */
    public function generateVideo(): File
    {
        return $this->generateVideoResult()->toFile();
    }
    /**
     * Generates multiple videos from the prompt.
     *
     * @since 1.3.0
     *
     * @param int|null $candidateCount The number of videos to generate.
     * @return list<File> The generated video files.
     * @throws InvalidArgumentException If the prompt or model validation fails.
     * @throws RuntimeException If no videos are generated.
     */
    public function generateVideos(?int $candidateCount = null): array
    {
        if ($candidateCount !== null) {
            $this->usingCandidateCount($candidateCount);
        }
        return $this->generateVideoResult()->toFiles();
    }
    /**
     * Appends a MessagePart to the messages array.
     *
     * If the last message has a user role, the part is added to it.
     * Otherwise, a new UserMessage is created with the part.
     *
     * @since 0.1.0
     *
     * @param MessagePart $part The part to append.
     * @return void
     */
    protected function appendPartToMessages(MessagePart $part): void
    {
        $lastMessage = end($this->messages);
        if ($lastMessage instanceof Message && $lastMessage->getRole()->isUser()) {
            // Replace the last message with a new one containing the appended part
            array_pop($this->messages);
            $this->messages[] = $lastMessage->withPart($part);
            return;
        }
        // Create new UserMessage with the part
        $this->messages[] = new UserMessage([$part]);
    }
    /**
     * Gets the model to use for generation.
     *
     * If a model has been explicitly set, validates it meets requirements and returns it.
     * Otherwise, finds a suitable model based on the prompt requirements.
     *
     * @since 0.1.0
     *
     * @param CapabilityEnum $capability The capability the model will be using.
     * @return ModelInterface The model to use.
     * @throws InvalidArgumentException If no suitable model is found or set model doesn't meet requirements.
     */
    private function getConfiguredModel(CapabilityEnum $capability): ModelInterface
    {
        $requirements = ModelRequirements::fromPromptData($capability, $this->messages, $this->modelConfig);
        if ($this->model !== null) {
            // Explicit model was provided via usingModel(); just update config and bind dependencies.
            $model = $this->model;
            $model->setConfig($this->modelConfig);
            $this->registry->bindModelDependencies($model);
            $this->bindModelRequestOptions($model);
            return $model;
        }
        // Retrieve the candidate models map which satisfies the requirements.
        $candidateMap = $this->getCandidateModelsMap($requirements);
        if (empty($candidateMap)) {
            $message = sprintf('No models found that support %s for this prompt.', $capability->value);
            if ($this->providerIdOrClassName !== null) {
                $message = sprintf('No models found for provider "%s" that support %s for this prompt.', $this->providerIdOrClassName, $capability->value);
            }
            throw new InvalidArgumentException($message);
        }
        // Check if any preferred models match the candidates, in priority order.
        if (!empty($this->modelPreferenceKeys)) {
            // Find preferences that match available candidates, preserving preference order.
            $matchingPreferences = array_intersect_key(array_flip($this->modelPreferenceKeys), $candidateMap);
            if (!empty($matchingPreferences)) {
                // Get the first matching preference key
                $firstMatchKey = key($matchingPreferences);
                [$providerId, $modelId] = $candidateMap[$firstMatchKey];
                $model = $this->registry->getProviderModel($providerId, $modelId, $this->modelConfig);
                $this->bindModelRequestOptions($model);
                return $model;
            }
        }
        // No preference matched; fall back to the first candidate discovered.
        [$providerId, $modelId] = reset($candidateMap);
        $model = $this->registry->getProviderModel($providerId, $modelId, $this->modelConfig);
        $this->bindModelRequestOptions($model);
        return $model;
    }
    /**
     * Binds configured request options to the model if present and supported.
     *
     * Request options are only applicable to API-based models that make HTTP requests.
     *
     * @since 0.3.0
     *
     * @param ModelInterface $model The model to bind request options to.
     * @return void
     */
    private function bindModelRequestOptions(ModelInterface $model): void
    {
        if ($this->requestOptions !== null && $model instanceof ApiBasedModelInterface) {
            $model->setRequestOptions($this->requestOptions);
        }
    }
    /**
     * Builds a map of candidate models that satisfy the requirements for efficient lookup.
     *
     * @since 0.2.0
     *
     * @param ModelRequirements $requirements The requirements derived from the prompt.
     * @return array<string, array{0:string,1:string}> Map of preference keys to [providerId, modelId] tuples.
     */
    private function getCandidateModelsMap(ModelRequirements $requirements): array
    {
        if ($this->providerIdOrClassName === null) {
            // No provider locked in, gather all models across providers that meet requirements.
            $providerModelsMetadata = $this->registry->findModelsMetadataForSupport($requirements);
            $candidateMap = [];
            foreach ($providerModelsMetadata as $providerModels) {
                $providerId = $providerModels->getProvider()->getId();
                $providerMap = $this->generateMapFromCandidates($providerId, $providerModels->getModels());
                // Use + operator to merge, preserving keys from $candidateMap (first provider wins for model-only keys)
                $candidateMap = $candidateMap + $providerMap;
            }
            return $candidateMap;
        }
        // Provider set, only consider models from that provider.
        $modelsMetadata = $this->registry->findProviderModelsMetadataForSupport($this->providerIdOrClassName, $requirements);
        // Ensure we pass the provider ID, not the class name
        $providerId = $this->registry->getProviderId($this->providerIdOrClassName);
        return $this->generateMapFromCandidates($providerId, $modelsMetadata);
    }
    /**
     * Generates a candidate map from model metadata with both provider-specific and model-only keys.
     *
     * @since 0.2.0
     *
     * @param string $providerId The provider ID.
     * @param list<ModelMetadata> $modelsMetadata The models metadata to map.
     * @return array<string, array{0:string,1:string}> Map of preference keys to [providerId, modelId] tuples.
     */
    private function generateMapFromCandidates(string $providerId, array $modelsMetadata): array
    {
        $map = [];
        foreach ($modelsMetadata as $modelMetadata) {
            $modelId = $modelMetadata->getId();
            // Add provider-specific key
            $providerModelKey = $this->createProviderModelPreferenceKey($providerId, $modelId);
            $map[$providerModelKey] = [$providerId, $modelId];
            // Add model-only key
            $modelKey = $this->createModelPreferenceKey($modelId);
            $map[$modelKey] = [$providerId, $modelId];
        }
        return $map;
    }
    /**
     * Normalizes and validates a preference identifier string.
     *
     * @since 0.2.0
     *
     * @param mixed $value The value to normalize.
     * @param string $emptyMessage The message for empty or invalid values.
     * @return string The normalized identifier.
     *
     * @throws InvalidArgumentException If the value is not a non-empty string.
     */
    private function normalizePreferenceIdentifier($value, string $emptyMessage = 'Model preference identifiers cannot be empty.'): string
    {
        if (!is_string($value)) {
            throw new InvalidArgumentException($emptyMessage);
        }
        $trimmed = trim($value);
        if ($trimmed === '') {
            throw new InvalidArgumentException($emptyMessage);
        }
        return $trimmed;
    }
    /**
     * Creates a preference key for a provider/model combination.
     *
     * @since 0.2.0
     *
     * @param string $providerId The provider identifier.
     * @param string $modelId The model identifier.
     * @return string The generated preference key.
     */
    private function createProviderModelPreferenceKey(string $providerId, string $modelId): string
    {
        return 'providerModel::' . $providerId . '::' . $modelId;
    }
    /**
     * Creates a preference key for a model identifier.
     *
     * @since 0.2.0
     *
     * @param string $modelId The model identifier.
     * @return string The generated preference key.
     */
    private function createModelPreferenceKey(string $modelId): string
    {
        return 'model::' . $modelId;
    }
    /**
     * Parses various input types into a Message with the given role.
     *
     * @since 0.1.0
     *
     * @param mixed $input The input to parse.
     * @param MessageRoleEnum $defaultRole The role for the message if not specified by input.
     * @return Message The parsed message.
     * @throws InvalidArgumentException If the input type is not supported or results in empty message.
     */
    private function parseMessage($input, MessageRoleEnum $defaultRole): Message
    {
        // Handle Message input directly
        if ($input instanceof Message) {
            return $input;
        }
        // Handle single MessagePart
        if ($input instanceof MessagePart) {
            return new Message($defaultRole, [$input]);
        }
        // Handle string input
        if (is_string($input)) {
            if (trim($input) === '') {
                throw new InvalidArgumentException('Cannot create a message from an empty string.');
            }
            return new Message($defaultRole, [new MessagePart($input)]);
        }
        // Handle array input
        if (!is_array($input)) {
            throw new InvalidArgumentException('Input must be a string, MessagePart, MessagePartArrayShape, ' . 'a list of string|MessagePart|MessagePartArrayShape, or a Message instance.');
        }
        // Handle MessageArrayShape input
        if (Message::isArrayShape($input)) {
            return Message::fromArray($input);
        }
        // Check if it's a MessagePartArrayShape
        if (MessagePart::isArrayShape($input)) {
            return new Message($defaultRole, [MessagePart::fromArray($input)]);
        }
        // It should be a list of string|MessagePart|MessagePartArrayShape
        if (!array_is_list($input)) {
            throw new InvalidArgumentException('Array input must be a list array.');
        }
        // Empty array check
        if (empty($input)) {
            throw new InvalidArgumentException('Cannot create a message from an empty array.');
        }
        $parts = [];
        foreach ($input as $item) {
            if (is_string($item)) {
                $parts[] = new MessagePart($item);
            } elseif ($item instanceof MessagePart) {
                $parts[] = $item;
            } elseif (is_array($item) && MessagePart::isArrayShape($item)) {
                $parts[] = MessagePart::fromArray($item);
            } else {
                throw new InvalidArgumentException('Array items must be strings, MessagePart instances, or MessagePartArrayShape.');
            }
        }
        return new Message($defaultRole, $parts);
    }
    /**
     * Validates the messages array for prompt generation.
     *
     * Ensures that:
     * - The first message is a user message
     * - The last message is a user message
     * - The last message has parts
     *
     * @since 0.1.0
     *
     * @return void
     * @throws InvalidArgumentException If validation fails.
     */
    private function validateMessages(): void
    {
        if (empty($this->messages)) {
            throw new InvalidArgumentException('Cannot generate from an empty prompt. Add content using withText() or similar methods.');
        }
        $firstMessage = reset($this->messages);
        if (!$firstMessage->getRole()->isUser()) {
            throw new InvalidArgumentException('The first message must be from a user role, not from ' . $firstMessage->getRole()->value);
        }
        $lastMessage = end($this->messages);
        if (!$lastMessage->getRole()->isUser()) {
            throw new InvalidArgumentException('The last message must be from a user role, not from ' . $lastMessage->getRole()->value);
        }
        if (empty($lastMessage->getParts())) {
            throw new InvalidArgumentException('The last message must have content parts. Add content using withText() or similar methods.');
        }
    }
    /**
     * Checks if the value is a list of Message objects.
     *
     * @since 0.1.0
     *
     * @param mixed $value The value to check.
     * @return bool True if the value is a list of Message objects.
     *
     * @phpstan-assert-if-true list<Message> $value
     */
    private function isMessagesList($value): bool
    {
        if (!is_array($value) || empty($value) || !array_is_list($value)) {
            return \false;
        }
        // Check if all items are Messages
        foreach ($value as $item) {
            if (!$item instanceof Message) {
                return \false;
            }
        }
        return \true;
    }
    /**
     * Includes output modalities if not already present.
     *
     * Adds the given modalities to the output modalities list if they're not
     * already included. If output modalities is null, initializes it with
     * the given modalities.
     *
     * @since 0.1.0
     *
     * @param ModalityEnum ...$modalities The modalities to include.
     * @return void
     */
    private function includeOutputModalities(ModalityEnum ...$modalities): void
    {
        $existing = $this->modelConfig->getOutputModalities();
        // Initialize if null
        if ($existing === null) {
            $this->modelConfig->setOutputModalities($modalities);
            return;
        }
        // Build a set of existing modality values for O(1) lookup
        $existingValues = [];
        foreach ($existing as $existingModality) {
            $existingValues[$existingModality->value] = \true;
        }
        // Add new modalities that don't exist
        $toAdd = [];
        foreach ($modalities as $modality) {
            if (!isset($existingValues[$modality->value])) {
                $toAdd[] = $modality;
            }
        }
        // Update if we have new modalities to add
        if (!empty($toAdd)) {
            $this->modelConfig->setOutputModalities(array_merge($existing, $toAdd));
        }
    }
    /**
     * Dispatches an event if an event dispatcher is registered.
     *
     * @since 0.4.0
     *
     * @param object $event The event to dispatch.
     * @return void
     */
    private function dispatchEvent(object $event): void
    {
        if ($this->eventDispatcher !== null) {
            $this->eventDispatcher->dispatch($event);
        }
    }
}
PK�e]0*�a��Builders/MessageBuilder.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Builders;

use InvalidArgumentException;
use WordPress\AiClient\Files\DTO\File;
use WordPress\AiClient\Messages\DTO\Message;
use WordPress\AiClient\Messages\DTO\MessagePart;
use WordPress\AiClient\Messages\Enums\MessageRoleEnum;
use WordPress\AiClient\Tools\DTO\FunctionCall;
use WordPress\AiClient\Tools\DTO\FunctionResponse;
/**
 * Fluent builder for constructing AI messages.
 *
 * This class provides a fluent interface for building messages with various
 * content types including text, files, function calls, and function responses.
 *
 * @since 0.2.0
 *
 * @phpstan-import-type MessagePartArrayShape from MessagePart
 *
 * @phpstan-type Input string|MessagePart|MessagePartArrayShape|File|FunctionCall|FunctionResponse|null
 */
class MessageBuilder
{
    /**
     * @var MessageRoleEnum|null The role of the message sender.
     */
    protected ?MessageRoleEnum $role = null;
    /**
     * @var list<MessagePart> The parts that make up the message.
     */
    protected array $parts = [];
    /**
     * Constructor.
     *
     * @since 0.2.0
     *
     * @param Input $input Optional initial content.
     * @param MessageRoleEnum|null $role Optional role.
     */
    public function __construct($input = null, ?MessageRoleEnum $role = null)
    {
        $this->role = $role;
        if ($input === null) {
            return;
        }
        // Handle different input types
        if ($input instanceof MessagePart) {
            $this->parts[] = $input;
        } elseif (is_string($input)) {
            $this->withText($input);
        } elseif ($input instanceof File) {
            $this->withFile($input);
        } elseif ($input instanceof FunctionCall) {
            $this->withFunctionCall($input);
        } elseif ($input instanceof FunctionResponse) {
            $this->withFunctionResponse($input);
        } elseif (is_array($input) && MessagePart::isArrayShape($input)) {
            $this->parts[] = MessagePart::fromArray($input);
        } else {
            throw new InvalidArgumentException('Input must be a string, MessagePart, MessagePartArrayShape, File, FunctionCall, or FunctionResponse.');
        }
    }
    /**
     * Creates a deep clone of this builder.
     *
     * Clones all MessagePart objects in the parts array to ensure
     * the cloned builder is independent of the original.
     *
     * @since 0.4.2
     */
    public function __clone()
    {
        // Deep clone parts array (MessagePart has __clone)
        $clonedParts = [];
        foreach ($this->parts as $part) {
            $clonedParts[] = clone $part;
        }
        $this->parts = $clonedParts;
        // Note: $role is an enum value object and can be safely shared
    }
    /**
     * Sets the role of the message sender.
     *
     * @since 0.2.0
     *
     * @param MessageRoleEnum $role The role to set.
     * @return self
     */
    public function usingRole(MessageRoleEnum $role): self
    {
        $this->role = $role;
        return $this;
    }
    /**
     * Sets the role to user.
     *
     * @since 0.2.0
     *
     * @return self
     */
    public function usingUserRole(): self
    {
        return $this->usingRole(MessageRoleEnum::user());
    }
    /**
     * Sets the role to model.
     *
     * @since 0.2.0
     *
     * @return self
     */
    public function usingModelRole(): self
    {
        return $this->usingRole(MessageRoleEnum::model());
    }
    /**
     * Adds text content to the message.
     *
     * @since 0.2.0
     *
     * @param string $text The text to add.
     * @return self
     * @throws InvalidArgumentException If the text is empty.
     */
    public function withText(string $text): self
    {
        if (trim($text) === '') {
            throw new InvalidArgumentException('Text content cannot be empty.');
        }
        $this->parts[] = new MessagePart($text);
        return $this;
    }
    /**
     * Adds a file to the message.
     *
     * Accepts:
     * - File object
     * - URL string (remote file)
     * - Base64-encoded data string
     * - Data URI string (data:mime/type;base64,data)
     * - Local file path string
     *
     * @since 0.2.0
     *
     * @param string|File $file The file to add.
     * @param string|null $mimeType Optional MIME type (ignored if File object provided).
     * @return self
     * @throws InvalidArgumentException If the file is invalid.
     */
    public function withFile($file, ?string $mimeType = null): self
    {
        $file = $file instanceof File ? $file : new File($file, $mimeType);
        $this->parts[] = new MessagePart($file);
        return $this;
    }
    /**
     * Adds a function call to the message.
     *
     * @since 0.2.0
     *
     * @param FunctionCall $functionCall The function call to add.
     * @return self
     */
    public function withFunctionCall(FunctionCall $functionCall): self
    {
        $this->parts[] = new MessagePart($functionCall);
        return $this;
    }
    /**
     * Adds a function response to the message.
     *
     * @since 0.2.0
     *
     * @param FunctionResponse $functionResponse The function response to add.
     * @return self
     */
    public function withFunctionResponse(FunctionResponse $functionResponse): self
    {
        $this->parts[] = new MessagePart($functionResponse);
        return $this;
    }
    /**
     * Adds multiple message parts to the message.
     *
     * @since 0.2.0
     *
     * @param MessagePart ...$parts The message parts to add.
     * @return self
     */
    public function withMessageParts(MessagePart ...$parts): self
    {
        foreach ($parts as $part) {
            $this->parts[] = $part;
        }
        return $this;
    }
    /**
     * Builds and returns the Message object.
     *
     * @since 0.2.0
     *
     * @return Message The built message.
     * @throws InvalidArgumentException If the message validation fails.
     */
    public function get(): Message
    {
        if (empty($this->parts)) {
            throw new InvalidArgumentException('Cannot build an empty message. Add content using withText() or similar methods.');
        }
        if ($this->role === null) {
            throw new InvalidArgumentException('Cannot build a message with no role. Set a role using usingRole() or similar methods.');
        }
        // At this point, we've validated that $this->role is not null
        /** @var MessageRoleEnum $role */
        $role = $this->role;
        return new Message($role, $this->parts);
    }
}
PK�e])@K�llFiles/ValueObjects/MimeType.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Files\ValueObjects;

use WordPress\AiClient\Common\Exception\InvalidArgumentException;
/**
 * Value object representing a MIME type.
 *
 * This immutable value object encapsulates MIME type validation and
 * provides convenient methods for checking MIME type categories.
 *
 * @since 0.1.0
 */
final class MimeType
{
    /**
     * @var string The MIME type value.
     */
    private string $value;
    /**
     * Common MIME type mappings for file extensions.
     *
     * @var array<string, string>
     */
    private static array $extensionMap = [
        // Text
        'txt' => 'text/plain',
        'html' => 'text/html',
        'htm' => 'text/html',
        'css' => 'text/css',
        'js' => 'application/javascript',
        'json' => 'application/json',
        'xml' => 'application/xml',
        'csv' => 'text/csv',
        'md' => 'text/markdown',
        // Images
        'jpg' => 'image/jpeg',
        'jpeg' => 'image/jpeg',
        'png' => 'image/png',
        'gif' => 'image/gif',
        'bmp' => 'image/bmp',
        'webp' => 'image/webp',
        'svg' => 'image/svg+xml',
        'ico' => 'image/x-icon',
        // Documents
        'pdf' => 'application/pdf',
        'doc' => 'application/msword',
        'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        'xls' => 'application/vnd.ms-excel',
        'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        'ppt' => 'application/vnd.ms-powerpoint',
        'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
        'odt' => 'application/vnd.oasis.opendocument.text',
        'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
        // Archives
        'zip' => 'application/zip',
        'tar' => 'application/x-tar',
        'gz' => 'application/gzip',
        'rar' => 'application/x-rar-compressed',
        '7z' => 'application/x-7z-compressed',
        // Audio
        'mp3' => 'audio/mpeg',
        'wav' => 'audio/wav',
        'ogg' => 'audio/ogg',
        'flac' => 'audio/flac',
        'm4a' => 'audio/m4a',
        'aac' => 'audio/aac',
        // Video
        'mp4' => 'video/mp4',
        'avi' => 'video/x-msvideo',
        'mov' => 'video/quicktime',
        'wmv' => 'video/x-ms-wmv',
        'flv' => 'video/x-flv',
        'webm' => 'video/webm',
        'mkv' => 'video/x-matroska',
        // Fonts
        'ttf' => 'font/ttf',
        'otf' => 'font/otf',
        'woff' => 'font/woff',
        'woff2' => 'font/woff2',
        // Other
        'php' => 'application/x-httpd-php',
        'sh' => 'application/x-sh',
        'exe' => 'application/x-msdownload',
    ];
    /**
     * Document MIME types.
     *
     * @var array<string>
     */
    private static array $documentTypes = ['application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/vnd.oasis.opendocument.text', 'application/vnd.oasis.opendocument.spreadsheet'];
    /**
     * Constructor.
     *
     * @since 0.1.0
     *
     * @param string $value The MIME type value.
     * @throws InvalidArgumentException If the MIME type is invalid.
     */
    public function __construct(string $value)
    {
        if (!self::isValid($value)) {
            throw new InvalidArgumentException(sprintf('Invalid MIME type: %s', $value));
        }
        $this->value = strtolower($value);
    }
    /**
     * Gets the primary known file extension for this MIME type.
     *
     * @since 0.1.0
     *
     * @return string The file extension (without the dot).
     * @throws InvalidArgumentException If no known extension exists for this MIME type.
     */
    public function toExtension(): string
    {
        // Reverse lookup for the MIME type to find the extension.
        $extension = array_search($this->value, self::$extensionMap, \true);
        if ($extension === \false) {
            throw new InvalidArgumentException(sprintf('No known extension for MIME type: %s', $this->value));
        }
        return $extension;
    }
    /**
     * Creates a MimeType from a file extension.
     *
     * @since 0.1.0
     *
     * @param string $extension The file extension (without the dot).
     * @return self The MimeType instance.
     * @throws InvalidArgumentException If the extension is not recognized.
     */
    public static function fromExtension(string $extension): self
    {
        $extension = strtolower($extension);
        if (!isset(self::$extensionMap[$extension])) {
            throw new InvalidArgumentException(sprintf('Unknown file extension: %s', $extension));
        }
        return new self(self::$extensionMap[$extension]);
    }
    /**
     * Checks if a MIME type string is valid.
     *
     * @since 0.1.0
     *
     * @param string $mimeType The MIME type to validate.
     * @return bool True if valid.
     */
    public static function isValid(string $mimeType): bool
    {
        // Basic MIME type validation: type/subtype
        return (bool) preg_match('/^[a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_+.]*\/[a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_+.]*$/', $mimeType);
    }
    /**
     * Checks if this MIME type is a specific type.
     *
     * This method returns true when the stored MIME type begins with the
     * given prefix. For example, `"audio"` matches `"audio/mpeg"`.
     *
     * @since 0.1.0
     *
     * @param string $mimeType The MIME type prefix to check (e.g., "audio", "image").
     * @return bool True if this MIME type is of the specified type.
     */
    public function isType(string $mimeType): bool
    {
        return str_starts_with($this->value, strtolower($mimeType) . '/');
    }
    /**
     * Checks if this is an image MIME type.
     *
     * @since 0.1.0
     *
     * @return bool True if this is an image type.
     */
    public function isImage(): bool
    {
        return $this->isType('image');
    }
    /**
     * Checks if this is an audio MIME type.
     *
     * @since 0.1.0
     *
     * @return bool True if this is an audio type.
     */
    public function isAudio(): bool
    {
        return $this->isType('audio');
    }
    /**
     * Checks if this is a video MIME type.
     *
     * @since 0.1.0
     *
     * @return bool True if this is a video type.
     */
    public function isVideo(): bool
    {
        return $this->isType('video');
    }
    /**
     * Checks if this is a text MIME type.
     *
     * @since 0.1.0
     *
     * @return bool True if this is a text type.
     */
    public function isText(): bool
    {
        return $this->isType('text');
    }
    /**
     * Checks if this is a document MIME type.
     *
     * @since 0.1.0
     *
     * @return bool True if this is a document type.
     */
    public function isDocument(): bool
    {
        return in_array($this->value, self::$documentTypes, \true);
    }
    /**
     * Checks if this MIME type equals another.
     *
     * @since 0.1.0
     *
     * @param self|string $other The other MIME type to compare.
     * @return bool True if equal.
     * @throws InvalidArgumentException If the other MIME type is invalid.
     */
    public function equals($other): bool
    {
        if ($other instanceof self) {
            return $this->value === $other->value;
        }
        if (is_string($other)) {
            return $this->value === strtolower($other);
        }
        throw new InvalidArgumentException(sprintf('Invalid MIME type comparison: %s', gettype($other)));
    }
    /**
     * Gets the string representation of the MIME type.
     *
     * @since 0.1.0
     *
     * @return string The MIME type value.
     */
    public function __toString(): string
    {
        return $this->value;
    }
}
PK�e]��ͷ�4�4Files/DTO/File.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Files\DTO;

use WordPress\AiClient\Common\AbstractDataTransferObject;
use WordPress\AiClient\Common\Exception\InvalidArgumentException;
use WordPress\AiClient\Common\Exception\RuntimeException;
use WordPress\AiClient\Files\Enums\FileTypeEnum;
use WordPress\AiClient\Files\ValueObjects\MimeType;
/**
 * Represents a file in the AI client.
 *
 * This DTO automatically detects whether a file is a URL, base64 data, or local file path
 * and handles them appropriately.
 *
 * @since 0.1.0
 *
 * @phpstan-type FileArrayShape array{
 *     fileType: string,
 *     url?: string,
 *     mimeType: string,
 *     base64Data?: string
 * }
 *
 * @extends AbstractDataTransferObject<FileArrayShape>
 */
class File extends AbstractDataTransferObject
{
    public const KEY_FILE_TYPE = 'fileType';
    public const KEY_MIME_TYPE = 'mimeType';
    public const KEY_URL = 'url';
    public const KEY_BASE64_DATA = 'base64Data';
    /**
     * @var MimeType The MIME type of the file.
     */
    private MimeType $mimeType;
    /**
     * @var FileTypeEnum The type of file storage.
     */
    private FileTypeEnum $fileType;
    /**
     * @var string|null The URL for remote files.
     */
    private ?string $url = null;
    /**
     * @var string|null The base64 data for inline files.
     */
    private ?string $base64Data = null;
    /**
     * Constructor.
     *
     * @since 0.1.0
     *
     * @param string $file The file string (URL, base64 data, or local path).
     * @param string|null $mimeType The MIME type of the file (optional).
     * @throws InvalidArgumentException If the file format is invalid or MIME type cannot be determined.
     */
    public function __construct(string $file, ?string $mimeType = null)
    {
        // Detect and process the file type (will set MIME type if possible)
        $this->detectAndProcessFile($file, $mimeType);
    }
    /**
     * Detects the file type and processes it accordingly.
     *
     * @since 0.1.0
     *
     * @param string $file The file string to process.
     * @param string|null $providedMimeType The explicitly provided MIME type.
     * @throws InvalidArgumentException If the file format is invalid or MIME type cannot be determined.
     */
    private function detectAndProcessFile(string $file, ?string $providedMimeType): void
    {
        // Check if it's a URL
        if ($this->isUrl($file)) {
            $this->fileType = FileTypeEnum::remote();
            $this->url = $file;
            $this->mimeType = $this->determineMimeType($providedMimeType, null, $file);
            return;
        }
        // Data URI pattern.
        $dataUriPattern = '/^data:(?:([a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_+.]*\/[a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_+.]*' . '(?:;[a-zA-Z0-9\-]+=[a-zA-Z0-9\-]+)*)?;)?base64,([A-Za-z0-9+\/]*={0,2})$/';
        // Check if it's a data URI.
        if (preg_match($dataUriPattern, $file, $matches)) {
            $this->fileType = FileTypeEnum::inline();
            $this->base64Data = $matches[2];
            // Extract just the base64 data
            $extractedMimeType = empty($matches[1]) ? null : $matches[1];
            $this->mimeType = $this->determineMimeType($providedMimeType, $extractedMimeType, null);
            return;
        }
        // Check if it's a local file path (before base64 check)
        if (file_exists($file) && is_file($file)) {
            $this->fileType = FileTypeEnum::inline();
            $this->base64Data = $this->convertFileToBase64($file);
            $this->mimeType = $this->determineMimeType($providedMimeType, null, $file);
            return;
        }
        // Check if it's plain base64
        if (preg_match('/^[A-Za-z0-9+\/]*={0,2}$/', $file)) {
            if ($providedMimeType === null) {
                throw new InvalidArgumentException('MIME type is required when providing plain base64 data without data URI format.');
            }
            $this->fileType = FileTypeEnum::inline();
            $this->base64Data = $file;
            $this->mimeType = new MimeType($providedMimeType);
            return;
        }
        throw new InvalidArgumentException('Invalid file provided. Expected URL, base64 data, or valid local file path.');
    }
    /**
     * Checks if a string is a valid URL.
     *
     * @since 0.1.0
     *
     * @param string $string The string to check.
     * @return bool True if the string is a URL.
     */
    private function isUrl(string $string): bool
    {
        return filter_var($string, \FILTER_VALIDATE_URL) !== \false && preg_match('/^https?:\/\//i', $string);
    }
    /**
     * Converts a local file to base64.
     *
     * @since 0.1.0
     *
     * @param string $filePath The path to the local file.
     * @return string The base64-encoded file data.
     * @throws RuntimeException If the file cannot be read.
     */
    private function convertFileToBase64(string $filePath): string
    {
        $fileContent = @file_get_contents($filePath);
        if ($fileContent === \false) {
            throw new RuntimeException(sprintf('Unable to read file: %s', $filePath));
        }
        return base64_encode($fileContent);
    }
    /**
     * Gets the file type.
     *
     * @since 0.1.0
     *
     * @return FileTypeEnum The file type.
     */
    public function getFileType(): FileTypeEnum
    {
        return $this->fileType;
    }
    /**
     * Checks if the file is an inline file.
     *
     * @since 0.1.0
     *
     * @return bool True if the file is inline (base64/data URI).
     */
    public function isInline(): bool
    {
        return $this->fileType->isInline();
    }
    /**
     * Checks if the file is a remote file.
     *
     * @since 0.1.0
     *
     * @return bool True if the file is remote (URL).
     */
    public function isRemote(): bool
    {
        return $this->fileType->isRemote();
    }
    /**
     * Gets the URL for remote files.
     *
     * @since 0.1.0
     *
     * @return string|null The URL, or null if not a remote file.
     */
    public function getUrl(): ?string
    {
        return $this->url;
    }
    /**
     * Gets the base64-encoded data for inline files.
     *
     * @since 0.1.0
     *
     * @return string|null The plain base64-encoded data (without data URI prefix), or null if not an inline file.
     */
    public function getBase64Data(): ?string
    {
        return $this->base64Data;
    }
    /**
     * Gets the data as a data URI for inline files.
     *
     * @since 0.1.0
     *
     * @return string|null The data URI in format: data:[mimeType];base64,[data], or null if not an inline file.
     */
    public function getDataUri(): ?string
    {
        if ($this->base64Data === null) {
            return null;
        }
        return sprintf('data:%s;base64,%s', $this->getMimeType(), $this->base64Data);
    }
    /**
     * Gets the MIME type of the file as a string.
     *
     * @since 0.1.0
     *
     * @return string The MIME type string value.
     */
    public function getMimeType(): string
    {
        return (string) $this->mimeType;
    }
    /**
     * Gets the MIME type object.
     *
     * @since 0.1.0
     *
     * @return MimeType The MIME type object.
     */
    public function getMimeTypeObject(): MimeType
    {
        return $this->mimeType;
    }
    /**
     * Checks if the file is a video.
     *
     * @since 0.1.0
     *
     * @return bool True if the file is a video.
     */
    public function isVideo(): bool
    {
        return $this->mimeType->isVideo();
    }
    /**
     * Checks if the file is an image.
     *
     * @since 0.1.0
     *
     * @return bool True if the file is an image.
     */
    public function isImage(): bool
    {
        return $this->mimeType->isImage();
    }
    /**
     * Checks if the file is audio.
     *
     * @since 0.1.0
     *
     * @return bool True if the file is audio.
     */
    public function isAudio(): bool
    {
        return $this->mimeType->isAudio();
    }
    /**
     * Checks if the file is text.
     *
     * @since 0.1.0
     *
     * @return bool True if the file is text.
     */
    public function isText(): bool
    {
        return $this->mimeType->isText();
    }
    /**
     * Checks if the file is a document.
     *
     * @since 0.1.0
     *
     * @return bool True if the file is a document.
     */
    public function isDocument(): bool
    {
        return $this->mimeType->isDocument();
    }
    /**
     * Checks if the file is a specific MIME type.
     *
     * @since 0.1.0
     *
     * @param string $type The mime type to check (e.g. 'image', 'text', 'video', 'audio').
     *
     * @return bool True if the file is of the specified type.
     */
    public function isMimeType(string $type): bool
    {
        return $this->mimeType->isType($type);
    }
    /**
     * Determines the MIME type from various sources.
     *
     * @since 0.1.0
     *
     * @param string|null $providedMimeType The explicitly provided MIME type.
     * @param string|null $extractedMimeType The MIME type extracted from data URI.
     * @param string|null $pathOrUrl The file path or URL to extract extension from.
     * @return MimeType The determined MIME type.
     * @throws InvalidArgumentException If MIME type cannot be determined.
     */
    private function determineMimeType(?string $providedMimeType, ?string $extractedMimeType, ?string $pathOrUrl): MimeType
    {
        // Prefer explicitly provided MIME type
        if ($providedMimeType !== null) {
            return new MimeType($providedMimeType);
        }
        // Use extracted MIME type from data URI
        if ($extractedMimeType !== null) {
            return new MimeType($extractedMimeType);
        }
        // Try to determine from file extension
        if ($pathOrUrl !== null) {
            $parsedUrl = parse_url($pathOrUrl);
            $path = $parsedUrl['path'] ?? $pathOrUrl;
            // Remove query string and fragment if present
            $cleanPath = strtok($path, '?#');
            if ($cleanPath === \false) {
                $cleanPath = $path;
            }
            $extension = pathinfo($cleanPath, \PATHINFO_EXTENSION);
            if (!empty($extension)) {
                try {
                    return MimeType::fromExtension($extension);
                } catch (InvalidArgumentException $e) {
                    // Extension not recognized, continue to error
                    unset($e);
                }
            }
        }
        throw new InvalidArgumentException('Unable to determine MIME type. Please provide it explicitly.');
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function getJsonSchema(): array
    {
        return ['type' => 'object', 'oneOf' => [['properties' => [self::KEY_FILE_TYPE => ['type' => 'string', 'const' => FileTypeEnum::REMOTE, 'description' => 'The file type.'], self::KEY_MIME_TYPE => ['type' => 'string', 'description' => 'The MIME type of the file.', 'pattern' => '^[a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_+.]*\/[a-zA-Z0-9]' . '[a-zA-Z0-9!#$&\-\^_+.]*$'], self::KEY_URL => ['type' => 'string', 'format' => 'uri', 'description' => 'The URL to the remote file.']], 'required' => [self::KEY_FILE_TYPE, self::KEY_MIME_TYPE, self::KEY_URL]], ['properties' => [self::KEY_FILE_TYPE => ['type' => 'string', 'const' => FileTypeEnum::INLINE, 'description' => 'The file type.'], self::KEY_MIME_TYPE => ['type' => 'string', 'description' => 'The MIME type of the file.', 'pattern' => '^[a-zA-Z0-9][a-zA-Z0-9!#$&\-\^_+.]*\/[a-zA-Z0-9]' . '[a-zA-Z0-9!#$&\-\^_+.]*$'], self::KEY_BASE64_DATA => ['type' => 'string', 'description' => 'The base64-encoded file data.']], 'required' => [self::KEY_FILE_TYPE, self::KEY_MIME_TYPE, self::KEY_BASE64_DATA]]]];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     *
     * @return FileArrayShape
     */
    public function toArray(): array
    {
        $data = [self::KEY_FILE_TYPE => $this->fileType->value, self::KEY_MIME_TYPE => $this->getMimeType()];
        if ($this->url !== null) {
            $data[self::KEY_URL] = $this->url;
        } elseif (!$this->fileType->isRemote() && $this->base64Data !== null) {
            $data[self::KEY_BASE64_DATA] = $this->base64Data;
        } else {
            throw new RuntimeException('File requires either url or base64Data. This should not be a possible condition.');
        }
        return $data;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function fromArray(array $array): self
    {
        static::validateFromArrayData($array, [self::KEY_FILE_TYPE]);
        // Check which properties are set to determine how to construct the File
        $mimeType = $array[self::KEY_MIME_TYPE] ?? null;
        if (isset($array[self::KEY_URL])) {
            return new self($array[self::KEY_URL], $mimeType);
        } elseif (isset($array[self::KEY_BASE64_DATA])) {
            return new self($array[self::KEY_BASE64_DATA], $mimeType);
        } else {
            throw new InvalidArgumentException('File requires either url or base64Data.');
        }
    }
    /**
     * Performs a deep clone of the file.
     *
     * This method ensures that the MimeType value object is cloned to prevent
     * any shared references between the original and cloned file.
     *
     * @since 0.4.2
     */
    public function __clone()
    {
        $this->mimeType = clone $this->mimeType;
    }
}
PK�e]��#c��$Files/Enums/MediaOrientationEnum.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Files\Enums;

use WordPress\AiClient\Common\AbstractEnum;
/**
 * Represents the type of file storage.
 *
 * @method static self square() Returns the square orientation
 * @method static self landscape() Returns the landscape orientation.
 * @method static self portrait() Returns the portrait orientation.
 * @method bool isSquare() Checks if this is an square orientation
 * @method bool isLandscape() Checks if this is a landscape orientation.
 * @method bool isPortrait() Checks if this is a portrait orientation.
 *
 * @since 0.1.0
 */
class MediaOrientationEnum extends AbstractEnum
{
    /**
     * Square orientation.
     *
     * @var string
     */
    public const SQUARE = 'square';
    /**
     * Landscape orientation.
     *
     * @var string
     */
    public const LANDSCAPE = 'landscape';
    /**
     * Portrait orientation.
     *
     * @var string
     */
    public const PORTRAIT = 'portrait';
}
PK�e]������Files/Enums/FileTypeEnum.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Files\Enums;

use WordPress\AiClient\Common\AbstractEnum;
/**
 * Represents the type of file storage.
 *
 * @method static self inline() Returns the inline file type.
 * @method static self remote() Returns the remote file type.
 * @method bool isInline() Checks if this is an inline file type.
 * @method bool isRemote() Checks if this is a remote file type.
 *
 * @since 0.1.0
 */
class FileTypeEnum extends AbstractEnum
{
    /**
     * Inline file with base64-encoded data.
     *
     * @var string
     */
    public const INLINE = 'inline';
    /**
     * Remote file referenced by URL.
     *
     * @var string
     */
    public const REMOTE = 'remote';
}
PK�e]
�µ}}%Common/AbstractDataTransferObject.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Common;

use JsonSerializable;
use stdClass;
use WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface;
use WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface;
use WordPress\AiClient\Common\Exception\InvalidArgumentException;
/**
 * Abstract base class for all Data Value Objects in the AI Client.
 *
 * This abstract class consolidates the common functionality needed by all
 * data transfer objects:
 * - Array transformation for data manipulation
 * - JSON schema support for validation and documentation
 * - JSON serialization with proper empty object handling
 *
 * All DTOs in the AI Client should extend this class to ensure
 * consistent behavior across the codebase.
 *
 * @since 0.1.0
 *
 * @template TArrayShape of array<string, mixed>
 * @implements WithArrayTransformationInterface<TArrayShape>
 */
abstract class AbstractDataTransferObject implements WithArrayTransformationInterface, WithJsonSchemaInterface, JsonSerializable
{
    /**
     * Validates that required keys exist in the array data.
     *
     * @since 0.1.0
     *
     * @param array<mixed> $data The array data to validate.
     * @param string[] $requiredKeys The keys that must be present.
     * @throws InvalidArgumentException If any required key is missing.
     */
    protected static function validateFromArrayData(array $data, array $requiredKeys): void
    {
        $missingKeys = [];
        foreach ($requiredKeys as $key) {
            if (!array_key_exists($key, $data)) {
                $missingKeys[] = $key;
            }
        }
        if (!empty($missingKeys)) {
            throw new InvalidArgumentException(sprintf('%s::fromArray() missing required keys: %s', static::class, implode(', ', $missingKeys)));
        }
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function isArrayShape(array $array): bool
    {
        try {
            /** @var TArrayShape $array */
            static::fromArray($array);
            return \true;
        } catch (InvalidArgumentException $e) {
            return \false;
        }
    }
    /**
     * Converts the object to a JSON-serializable format.
     *
     * This method uses the toArray() method and then processes the result
     * based on the JSON schema to ensure proper object representation for
     * empty arrays.
     *
     * @since 0.1.0
     *
     * @return mixed The JSON-serializable representation.
     */
    #[\ReturnTypeWillChange]
    public function jsonSerialize()
    {
        $data = $this->toArray();
        $schema = static::getJsonSchema();
        return $this->convertEmptyArraysToObjects($data, $schema);
    }
    /**
     * Recursively converts empty arrays to stdClass objects where the schema expects objects.
     *
     * @since 0.1.0
     *
     * @param mixed $data The data to process.
     * @param array<mixed, mixed> $schema The JSON schema for the data.
     * @return mixed The processed data.
     */
    private function convertEmptyArraysToObjects($data, array $schema)
    {
        // If data is an empty array and schema expects object, convert to stdClass
        if (is_array($data) && empty($data) && isset($schema['type']) && $schema['type'] === 'object') {
            return new stdClass();
        }
        // If data is an array with content, recursively process nested structures
        if (is_array($data)) {
            // Handle object properties
            if (isset($schema['properties']) && is_array($schema['properties'])) {
                foreach ($data as $key => $value) {
                    if (isset($schema['properties'][$key]) && is_array($schema['properties'][$key])) {
                        $data[$key] = $this->convertEmptyArraysToObjects($value, $schema['properties'][$key]);
                    }
                }
            }
            // Handle array items
            if (isset($schema['items']) && is_array($schema['items'])) {
                foreach ($data as $index => $item) {
                    $data[$index] = $this->convertEmptyArraysToObjects($item, $schema['items']);
                }
            }
            // Handle oneOf/anyOf schemas - just use the first one
            foreach (['oneOf', 'anyOf'] as $keyword) {
                if (isset($schema[$keyword]) && is_array($schema[$keyword])) {
                    foreach ($schema[$keyword] as $possibleSchema) {
                        if (is_array($possibleSchema)) {
                            return $this->convertEmptyArraysToObjects($data, $possibleSchema);
                        }
                    }
                }
            }
        }
        return $data;
    }
}
PK�e]�`]��&Common/Traits/WithDataCachingTrait.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Common\Traits;

use WordPress\AiClient\AiClient;
/**
 * Trait for objects that cache data using PSR-16 cache with in-memory fallback.
 *
 * When a PSR-16 cache is configured via AiClient::setCache(), data is stored persistently.
 * Otherwise, data is cached in-memory for the duration of the request.
 *
 * @since 0.4.0
 */
trait WithDataCachingTrait
{
    /**
     * In-memory cache used when no PSR-16 cache is configured.
     *
     * @since 0.4.0
     *
     * @var array<string, mixed>
     */
    private array $localCache = [];
    /**
     * Gets the cache key suffixes managed by this object.
     *
     * @since 0.4.0
     *
     * @return list<string> The cache key suffixes.
     */
    abstract protected function getCachedKeys(): array;
    /**
     * Gets the base cache key for this object.
     *
     * The base cache key is used as a prefix for all cache keys managed by this object.
     * It should be unique to the implementing class to avoid cache key collisions.
     *
     * @since 0.4.0
     *
     * @return string The base cache key.
     */
    abstract protected function getBaseCacheKey(): string;
    /**
     * Checks if a value exists in the cache.
     *
     * @since 0.4.0
     *
     * @param string $key The cache key suffix (will be appended to the base key).
     * @return bool True if the value exists in cache, false otherwise.
     */
    protected function hasCache(string $key): bool
    {
        $fullKey = $this->buildCacheKey($key);
        $cache = AiClient::getCache();
        if ($cache !== null) {
            return $cache->has($fullKey);
        }
        return array_key_exists($fullKey, $this->localCache);
    }
    /**
     * Gets a value from the cache, or computes and caches it if not present.
     *
     * @since 0.4.0
     *
     * @param string                 $key      The cache key suffix (will be appended to the base key).
     * @param callable               $callback The callback to compute the value if not cached.
     * @param int|\DateInterval|null $ttl      The TTL for the cache entry, or null for default.
     *                                         Ignored for local cache.
     * @return mixed The cached or computed value.
     */
    protected function cached(string $key, callable $callback, $ttl = null)
    {
        if ($this->hasCache($key)) {
            return $this->getCache($key);
        }
        $value = $callback();
        $this->setCache($key, $value, $ttl);
        return $value;
    }
    /**
     * Gets a value from the cache.
     *
     * @since 0.4.0
     *
     * @param string $key     The cache key suffix (will be appended to the base key).
     * @param mixed  $default The default value to return if the key does not exist.
     * @return mixed The cached value or the default value if not found.
     */
    protected function getCache(string $key, $default = null)
    {
        $fullKey = $this->buildCacheKey($key);
        $cache = AiClient::getCache();
        if ($cache !== null) {
            return $cache->get($fullKey, $default);
        }
        return $this->localCache[$fullKey] ?? $default;
    }
    /**
     * Sets a value in the cache.
     *
     * @since 0.4.0
     *
     * @param string                $key   The cache key suffix (will be appended to the base key).
     * @param mixed                 $value The value to cache.
     * @param int|\DateInterval|null $ttl   The TTL for the cache entry, or null for default. Ignored for local cache.
     * @return bool True on success, false on failure.
     */
    protected function setCache(string $key, $value, $ttl = null): bool
    {
        $fullKey = $this->buildCacheKey($key);
        $cache = AiClient::getCache();
        if ($cache !== null) {
            return $cache->set($fullKey, $value, $ttl);
        }
        $this->localCache[$fullKey] = $value;
        return \true;
    }
    /**
     * Invalidates all caches managed by this object.
     *
     * @since 0.4.0
     *
     * @return void
     */
    public function invalidateCaches(): void
    {
        foreach ($this->getCachedKeys() as $key) {
            $this->clearCache($key);
        }
    }
    /**
     * Clears a value from the cache.
     *
     * @since 0.4.0
     *
     * @param string $key The cache key suffix (will be appended to the base key).
     * @return bool True on success, false on failure.
     */
    protected function clearCache(string $key): bool
    {
        $fullKey = $this->buildCacheKey($key);
        $cache = AiClient::getCache();
        if ($cache !== null) {
            return $cache->delete($fullKey);
        }
        unset($this->localCache[$fullKey]);
        return \true;
    }
    /**
     * Builds the full cache key by combining the base key with the suffix.
     *
     * @since 0.4.0
     *
     * @param string $key The cache key suffix.
     * @return string The full cache key.
     */
    private function buildCacheKey(string $key): string
    {
        return $this->getBaseCacheKey() . '_' . $key;
    }
}
PK�e]PX�c��-Common/Exception/InvalidArgumentException.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Common\Exception;

use WordPress\AiClient\Common\Contracts\AiClientExceptionInterface;
/**
 * Exception thrown when an invalid argument is provided.
 *
 * This extends PHP's built-in InvalidArgumentException while implementing
 * the AI Client exception interface for consistent catch handling.
 *
 * @since 0.2.0
 */
class InvalidArgumentException extends \InvalidArgumentException implements AiClientExceptionInterface
{
}
PK�e]�e�,��/Common/Exception/TokenLimitReachedException.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Common\Exception;

/**
 * Exception thrown when a token limit is reached during prompt fulfillment.
 *
 * Providers should throw this exception when the token usage for a request
 * exceeds the allowed limit, whether that is the model's context window
 * or a configured maximum.
 *
 * @since 1.0.0
 */
class TokenLimitReachedException extends \WordPress\AiClient\Common\Exception\RuntimeException
{
    /**
     * The token limit that was reached, if known.
     *
     * @since 1.0.0
     *
     * @var int|null
     */
    private $maxTokens;
    /**
     * Creates a new TokenLimitReachedException.
     *
     * @since 1.0.0
     *
     * @param string         $message   The exception message.
     * @param int|null       $maxTokens The token limit that was reached, if known.
     * @param \Throwable|null $previous  The previous throwable used for exception chaining.
     */
    public function __construct(string $message = '', ?int $maxTokens = null, ?\Throwable $previous = null)
    {
        parent::__construct($message, 0, $previous);
        $this->maxTokens = $maxTokens;
    }
    /**
     * Returns the token limit that was reached, if known.
     *
     * @since 1.0.0
     *
     * @return int|null The token limit, or null if not provided.
     */
    public function getMaxTokens(): ?int
    {
        return $this->maxTokens;
    }
}
PK�e].�}��%Common/Exception/RuntimeException.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Common\Exception;

use WordPress\AiClient\Common\Contracts\AiClientExceptionInterface;
/**
 * Exception thrown for runtime errors.
 *
 * This extends PHP's built-in RuntimeException while implementing
 * the AI Client exception interface for consistent catch handling.
 *
 * @since 0.2.0
 */
class RuntimeException extends \RuntimeException implements AiClientExceptionInterface
{
}
PK�e]�M3Vp,p,Common/AbstractEnum.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Common;

use BadMethodCallException;
use JsonSerializable;
use ReflectionClass;
use WordPress\AiClient\Common\Exception\InvalidArgumentException;
use WordPress\AiClient\Common\Exception\RuntimeException;
/**
 * Abstract base class for enum-like behavior in PHP 7.4.
 *
 * This class provides enum-like functionality for PHP versions that don't support native enums.
 * Child classes should define uppercase snake_case constants for enum values.
 *
 * @example
 * class PersonEnum extends AbstractEnum {
 *     public const FIRST_NAME = 'first';
 *     public const LAST_NAME = 'last';
 * }
 *
 * // Usage:
 * $enum = PersonEnum::from('first'); // Creates instance with value 'first'
 * $enum = PersonEnum::tryFrom('invalid'); // Returns null
 * $enum = PersonEnum::firstName(); // Creates instance with value 'first'
 * $enum->name; // 'FIRST_NAME'
 * $enum->value; // 'first'
 * $enum->equals('first'); // Returns true
 * $enum->is(PersonEnum::firstName()); // Returns true
 * PersonEnum::cases(); // Returns array of all enum instances
 *
 * @property-read string $value The value of the enum instance.
 * @property-read string $name The name of the enum constant.
 *
 * @since 0.1.0
 */
abstract class AbstractEnum implements JsonSerializable
{
    /**
     * @var string The value of the enum instance.
     */
    private string $value;
    /**
     * @var string The name of the enum constant.
     */
    private string $name;
    /**
     * @var array<string, array<string, string>> Cache for reflection data.
     */
    private static array $cache = [];
    /**
     * @var array<string, array<string, self>> Cache for enum instances.
     */
    private static array $instances = [];
    /**
     * Constructor is private to ensure instances are created through static methods.
     *
     * @since 0.1.0
     *
     * @param string $value The enum value.
     * @param string $name The constant name.
     */
    final private function __construct(string $value, string $name)
    {
        $this->value = $value;
        $this->name = $name;
    }
    /**
     * Provides read-only access to properties.
     *
     * @since 0.1.0
     *
     * @param string $property The property name.
     * @return mixed The property value.
     * @throws BadMethodCallException If property doesn't exist.
     */
    final public function __get(string $property)
    {
        if ($property === 'value' || $property === 'name') {
            return $this->{$property};
        }
        throw new BadMethodCallException(sprintf('Property %s::%s does not exist', static::class, $property));
    }
    /**
     * Prevents property modification.
     *
     * @since 0.1.0
     *
     * @param string $property The property name.
     * @param mixed $value The value to set.
     * @throws BadMethodCallException Always, as enum properties are read-only.
     */
    final public function __set(string $property, $value): void
    {
        throw new BadMethodCallException(sprintf('Cannot modify property %s::%s - enum properties are read-only', static::class, $property));
    }
    /**
     * Creates an enum instance from a value, throws exception if invalid.
     *
     * @since 0.1.0
     *
     * @param string $value The enum value.
     * @return static The enum instance.
     * @throws InvalidArgumentException If the value is not valid.
     */
    final public static function from(string $value): self
    {
        $instance = self::tryFrom($value);
        if ($instance === null) {
            throw new InvalidArgumentException(sprintf('%s is not a valid backing value for enum %s', $value, static::class));
        }
        return $instance;
    }
    /**
     * Tries to create an enum instance from a value, returns null if invalid.
     *
     * @since 0.1.0
     *
     * @param string $value The enum value.
     * @return static|null The enum instance or null.
     */
    final public static function tryFrom(string $value): ?self
    {
        $constants = static::getConstants();
        foreach ($constants as $name => $constantValue) {
            if ($constantValue === $value) {
                return self::getInstance($constantValue, $name);
            }
        }
        return null;
    }
    /**
     * Gets all enum cases.
     *
     * @since 0.1.0
     *
     * @return static[] Array of all enum instances.
     */
    final public static function cases(): array
    {
        $cases = [];
        $constants = static::getConstants();
        foreach ($constants as $name => $value) {
            $cases[] = self::getInstance($value, $name);
        }
        return $cases;
    }
    /**
     * Checks if this enum has the same value as the given value.
     *
     * @since 0.1.0
     *
     * @param string|self $other The value or enum to compare.
     * @return bool True if values are equal.
     */
    final public function equals($other): bool
    {
        if ($other instanceof self) {
            return $this->is($other);
        }
        return $this->value === $other;
    }
    /**
     * Checks if this enum is the same instance type and value as another enum.
     *
     * @since 0.1.0
     *
     * @param self $other The other enum to compare.
     * @return bool True if enums are identical.
     */
    final public function is(self $other): bool
    {
        return $this === $other;
        // Since we're using singletons, we can use identity comparison
    }
    /**
     * Gets all valid values for this enum.
     *
     * @since 0.1.0
     *
     * @return string[] List of all enum values.
     */
    final public static function getValues(): array
    {
        return array_values(static::getConstants());
    }
    /**
     * Checks if a value is valid for this enum.
     *
     * @since 0.1.0
     *
     * @param string $value The value to check.
     * @return bool True if value is valid.
     */
    final public static function isValidValue(string $value): bool
    {
        return in_array($value, self::getValues(), \true);
    }
    /**
     * Gets or creates a singleton instance for the given value and name.
     *
     * @since 0.1.0
     *
     * @param string $value The enum value.
     * @param string $name The constant name.
     * @return static The enum instance.
     */
    private static function getInstance(string $value, string $name): self
    {
        $className = static::class;
        if (!isset(self::$instances[$className])) {
            self::$instances[$className] = [];
        }
        if (!isset(self::$instances[$className][$name])) {
            $instance = new $className($value, $name);
            self::$instances[$className][$name] = $instance;
        }
        /** @var static */
        return self::$instances[$className][$name];
    }
    /**
     * Gets all constants for this enum class.
     *
     * @since 0.1.0
     *
     * @return array<string, string> Map of constant names to values.
     * @throws RuntimeException If invalid constant found.
     */
    final protected static function getConstants(): array
    {
        $className = static::class;
        if (!isset(self::$cache[$className])) {
            self::$cache[$className] = static::determineClassEnumerations($className);
        }
        return self::$cache[$className];
    }
    /**
     * Determines the class enumerations by reflecting on class constants.
     *
     * This method can be overridden by subclasses to customize how
     * enumerations are determined (e.g., to add dynamic constants).
     *
     * @since 0.1.0
     *
     * @param class-string $className The fully qualified class name.
     * @return array<string, string> Map of constant names to values.
     * @throws RuntimeException If invalid constant found.
     */
    protected static function determineClassEnumerations(string $className): array
    {
        $reflection = new ReflectionClass($className);
        $constants = $reflection->getConstants();
        // Validate all constants
        $enumConstants = [];
        foreach ($constants as $name => $value) {
            // Check if constant name follows uppercase snake_case pattern
            if (!preg_match('/^[A-Z][A-Z0-9_]*$/', $name)) {
                throw new RuntimeException(sprintf('Invalid enum constant name "%s" in %s. Constants must be UPPER_SNAKE_CASE.', $name, $className));
            }
            // Check if value is valid type
            if (!is_string($value)) {
                throw new RuntimeException(sprintf('Invalid enum value type for constant %s::%s. ' . 'Only string values are allowed, %s given.', $className, $name, gettype($value)));
            }
            $enumConstants[$name] = $value;
        }
        return $enumConstants;
    }
    /**
     * Handles dynamic method calls for enum checking.
     *
     * @since 0.1.0
     *
     * @param string $name The method name.
     * @param array<mixed> $arguments The method arguments.
     * @return bool True if the enum value matches.
     * @throws BadMethodCallException If the method doesn't exist.
     */
    final public function __call(string $name, array $arguments): bool
    {
        // Handle is* methods
        if (str_starts_with($name, 'is')) {
            $constantName = self::camelCaseToConstant(substr($name, 2));
            $constants = static::getConstants();
            if (isset($constants[$constantName])) {
                return $this->value === $constants[$constantName];
            }
        }
        throw new BadMethodCallException(sprintf('Method %s::%s does not exist', static::class, $name));
    }
    /**
     * Handles static method calls for enum creation.
     *
     * @since 0.1.0
     *
     * @param string $name The method name.
     * @param array<mixed> $arguments The method arguments.
     * @return static The enum instance.
     * @throws BadMethodCallException If the method doesn't exist.
     */
    final public static function __callStatic(string $name, array $arguments): self
    {
        $constantName = self::camelCaseToConstant($name);
        $constants = static::getConstants();
        if (isset($constants[$constantName])) {
            return self::getInstance($constants[$constantName], $constantName);
        }
        throw new BadMethodCallException(sprintf('Method %s::%s does not exist', static::class, $name));
    }
    /**
     * Converts camelCase to CONSTANT_CASE.
     *
     * @since 0.1.0
     *
     * @param string $camelCase The camelCase string.
     * @return string The CONSTANT_CASE version.
     */
    private static function camelCaseToConstant(string $camelCase): string
    {
        $snakeCase = preg_replace('/([a-z])([A-Z])/', '$1_$2', $camelCase);
        if ($snakeCase === null) {
            return strtoupper($camelCase);
        }
        return strtoupper($snakeCase);
    }
    /**
     * Returns string representation of the enum.
     *
     * @since 0.1.0
     *
     * @return string The enum value.
     */
    final public function __toString(): string
    {
        return $this->value;
    }
    /**
     * Converts the enum to a JSON-serializable format.
     *
     * @since 0.1.0
     *
     * @return string The enum value.
     */
    #[\ReturnTypeWillChange]
    public function jsonSerialize()
    {
        return $this->value;
    }
}
PK�e]ꬖD5Common/Contracts/WithArrayTransformationInterface.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Common\Contracts;

/**
 * Interface for objects that support array transformation.
 *
 * @since 0.1.0
 *
 * @template TArrayShape of array<string, mixed>
 */
interface WithArrayTransformationInterface
{
    /**
     * Converts the object to an array representation.
     *
     * @since 0.1.0
     *
     * @return TArrayShape The array representation.
     */
    public function toArray(): array;
    /**
     * Creates an instance from array data.
     *
     * @since 0.1.0
     *
     * @param TArrayShape $array The array data.
     * @return self<TArrayShape> The created instance.
     */
    public static function fromArray(array $array): self;
    /**
     * Checks if the array is a valid shape for this object.
     *
     * @since 0.1.0
     *
     * @param array<mixed> $array The array to check.
     * @return bool True if the array is a valid shape.
     * @phpstan-assert-if-true TArrayShape $array
     */
    public static function isArrayShape(array $array): bool;
}
PK�e]���1^^,Common/Contracts/WithJsonSchemaInterface.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Common\Contracts;

/**
 * Interface for objects that can provide their JSON schema representation.
 *
 * This interface is implemented by DTOs to provide a consistent way to retrieve
 * their JSON schema for validation and serialization purposes.
 *
 * @since 0.1.0
 */
interface WithJsonSchemaInterface
{
    /**
     * Gets the JSON schema representation of the object.
     *
     * @since 0.1.0
     *
     * @return array<string, mixed> The JSON schema as an associative array.
     */
    public static function getJsonSchema(): array;
}
PK�e]�ED�bb(Common/Contracts/CachesDataInterface.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Common\Contracts;

/**
 * Interface for objects that cache data.
 *
 * @since 0.4.0
 */
interface CachesDataInterface
{
    /**
     * Invalidates all caches managed by this object.
     *
     * @since 0.4.0
     *
     * @return void
     */
    public function invalidateCaches(): void;
}
PK�e]=�5�WW/Common/Contracts/AiClientExceptionInterface.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Common\Contracts;

use Throwable;
/**
 * Base interface for all AI Client exceptions.
 *
 * This interface allows callers to catch all AI Client specific exceptions
 * with a single catch statement.
 *
 * @since 0.2.0
 */
interface AiClientExceptionInterface extends Throwable
{
}
PK�e]�e$$Messages/DTO/Message.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Messages\DTO;

use WordPress\AiClient\Common\AbstractDataTransferObject;
use WordPress\AiClient\Common\Exception\InvalidArgumentException;
use WordPress\AiClient\Messages\Enums\MessageRoleEnum;
/**
 * Represents a message in an AI conversation.
 *
 * Messages are the fundamental unit of communication with AI models,
 * containing a role and one or more parts with different content types.
 *
 * @since 0.1.0
 *
 * @phpstan-import-type MessagePartArrayShape from MessagePart
 *
 * @phpstan-type MessageArrayShape array{
 *     role: string,
 *     parts: array<MessagePartArrayShape>
 * }
 *
 * @extends AbstractDataTransferObject<MessageArrayShape>
 */
class Message extends AbstractDataTransferObject
{
    public const KEY_ROLE = 'role';
    public const KEY_PARTS = 'parts';
    /**
     * @var MessageRoleEnum The role of the message sender.
     */
    protected MessageRoleEnum $role;
    /**
     * @var MessagePart[] The parts that make up this message.
     */
    protected array $parts;
    /**
     * Constructor.
     *
     * @since 0.1.0
     *
     * @param MessageRoleEnum $role The role of the message sender.
     * @param MessagePart[] $parts The parts that make up this message.
     * @throws InvalidArgumentException If parts contain invalid content for the role.
     */
    public function __construct(MessageRoleEnum $role, array $parts)
    {
        $this->role = $role;
        $this->parts = $parts;
        $this->validateParts();
    }
    /**
     * Gets the role of the message sender.
     *
     * @since 0.1.0
     *
     * @return MessageRoleEnum The role.
     */
    public function getRole(): MessageRoleEnum
    {
        return $this->role;
    }
    /**
     * Gets the message parts.
     *
     * @since 0.1.0
     *
     * @return MessagePart[] The message parts.
     */
    public function getParts(): array
    {
        return $this->parts;
    }
    /**
     * Returns a new instance with the given part appended.
     *
     * @since 0.1.0
     *
     * @param MessagePart $part The part to append.
     * @return Message A new instance with the part appended.
     * @throws InvalidArgumentException If the part is invalid for the role.
     */
    public function withPart(\WordPress\AiClient\Messages\DTO\MessagePart $part): \WordPress\AiClient\Messages\DTO\Message
    {
        $newParts = $this->parts;
        $newParts[] = $part;
        return new \WordPress\AiClient\Messages\DTO\Message($this->role, $newParts);
    }
    /**
     * Validates that the message parts are appropriate for the message role.
     *
     * @since 0.1.0
     *
     * @return void
     * @throws InvalidArgumentException If validation fails.
     */
    private function validateParts(): void
    {
        foreach ($this->parts as $part) {
            $type = $part->getType();
            if ($this->role->isUser() && $type->isFunctionCall()) {
                throw new InvalidArgumentException('User messages cannot contain function calls.');
            }
            if ($this->role->isModel() && $type->isFunctionResponse()) {
                throw new InvalidArgumentException('Model messages cannot contain function responses.');
            }
        }
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function getJsonSchema(): array
    {
        return ['type' => 'object', 'properties' => [self::KEY_ROLE => ['type' => 'string', 'enum' => MessageRoleEnum::getValues(), 'description' => 'The role of the message sender.'], self::KEY_PARTS => ['type' => 'array', 'items' => \WordPress\AiClient\Messages\DTO\MessagePart::getJsonSchema(), 'minItems' => 1, 'description' => 'The parts that make up this message.']], 'required' => [self::KEY_ROLE, self::KEY_PARTS]];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     *
     * @return MessageArrayShape
     */
    public function toArray(): array
    {
        return [self::KEY_ROLE => $this->role->value, self::KEY_PARTS => array_map(function (\WordPress\AiClient\Messages\DTO\MessagePart $part) {
            return $part->toArray();
        }, $this->parts)];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     *
     * @return self The specific message class based on the role.
     */
    final public static function fromArray(array $array): self
    {
        static::validateFromArrayData($array, [self::KEY_ROLE, self::KEY_PARTS]);
        $role = MessageRoleEnum::from($array[self::KEY_ROLE]);
        $partsData = $array[self::KEY_PARTS];
        $parts = array_map(function (array $partData) {
            return \WordPress\AiClient\Messages\DTO\MessagePart::fromArray($partData);
        }, $partsData);
        // Determine which concrete class to instantiate based on role
        if ($role->isUser()) {
            return new \WordPress\AiClient\Messages\DTO\UserMessage($parts);
        } elseif ($role->isModel()) {
            return new \WordPress\AiClient\Messages\DTO\ModelMessage($parts);
        } else {
            // Only USER and MODEL roles are supported
            throw new InvalidArgumentException('Invalid message role: ' . $role->value);
        }
    }
    /**
     * Performs a deep clone of the message.
     *
     * This method ensures that message part objects are cloned to prevent
     * modifications to the cloned message from affecting the original.
     *
     * @since 0.4.2
     */
    public function __clone()
    {
        $clonedParts = [];
        foreach ($this->parts as $part) {
            $clonedParts[] = clone $part;
        }
        $this->parts = $clonedParts;
    }
}
PK�e]�論ddMessages/DTO/ModelMessage.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Messages\DTO;

use WordPress\AiClient\Messages\Enums\MessageRoleEnum;
/**
 * Represents a message from the AI model.
 *
 * This is a convenience class that automatically sets the role to MODEL.
 * Model messages contain the AI's responses.
 *
 * Important: Do not rely on `instanceof ModelMessage` to determine the message role.
 * This is merely a helper class for construction. Always use `$message->getRole()`
 * to check the role of a message.
 *
 * @since 0.1.0
 */
class ModelMessage extends \WordPress\AiClient\Messages\DTO\Message
{
    /**
     * Constructor.
     *
     * @since 0.1.0
     *
     * @param MessagePart[] $parts The parts that make up this message.
     */
    public function __construct(array $parts)
    {
        parent::__construct(MessageRoleEnum::model(), $parts);
    }
}
PK�e]��M�,,Messages/DTO/UserMessage.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Messages\DTO;

use WordPress\AiClient\Messages\Enums\MessageRoleEnum;
/**
 * Represents a message from a user.
 *
 * This is a convenience class that automatically sets the role to USER.
 *
 * Important: Do not rely on `instanceof UserMessage` to determine the message role.
 * This is merely a helper class for construction. Always use `$message->getRole()`
 * to check the role of a message.
 *
 * @since 0.1.0
 */
class UserMessage extends \WordPress\AiClient\Messages\DTO\Message
{
    /**
     * Constructor.
     *
     * @since 0.1.0
     *
     * @param MessagePart[] $parts The parts that make up this message.
     */
    public function __construct(array $parts)
    {
        parent::__construct(MessageRoleEnum::user(), $parts);
    }
}
PK�e]"M�X-*-*Messages/DTO/MessagePart.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Messages\DTO;

use WordPress\AiClient\Common\AbstractDataTransferObject;
use WordPress\AiClient\Common\Exception\InvalidArgumentException;
use WordPress\AiClient\Common\Exception\RuntimeException;
use WordPress\AiClient\Files\DTO\File;
use WordPress\AiClient\Messages\Enums\MessagePartChannelEnum;
use WordPress\AiClient\Messages\Enums\MessagePartTypeEnum;
use WordPress\AiClient\Tools\DTO\FunctionCall;
use WordPress\AiClient\Tools\DTO\FunctionResponse;
/**
 * Represents a part of a message.
 *
 * Messages can contain multiple parts of different types, such as text, files,
 * function calls, etc. This DTO encapsulates one such part.
 *
 * @since 0.1.0
 *
 * @phpstan-import-type FileArrayShape from File
 * @phpstan-import-type FunctionCallArrayShape from FunctionCall
 * @phpstan-import-type FunctionResponseArrayShape from FunctionResponse
 *
 * @phpstan-type MessagePartArrayShape array{
 *     channel: string,
 *     type: string,
 *     thoughtSignature?: string,
 *     text?: string,
 *     file?: FileArrayShape,
 *     functionCall?: FunctionCallArrayShape,
 *     functionResponse?: FunctionResponseArrayShape
 * }
 *
 * @extends AbstractDataTransferObject<MessagePartArrayShape>
 */
class MessagePart extends AbstractDataTransferObject
{
    public const KEY_CHANNEL = 'channel';
    public const KEY_TYPE = 'type';
    public const KEY_THOUGHT_SIGNATURE = 'thoughtSignature';
    public const KEY_TEXT = 'text';
    public const KEY_FILE = 'file';
    public const KEY_FUNCTION_CALL = 'functionCall';
    public const KEY_FUNCTION_RESPONSE = 'functionResponse';
    /**
     * @var MessagePartChannelEnum The channel this message part belongs to.
     */
    private MessagePartChannelEnum $channel;
    /**
     * @var MessagePartTypeEnum The type of this message part.
     */
    private MessagePartTypeEnum $type;
    /**
     * @var string|null Thought signature for extended thinking.
     */
    private ?string $thoughtSignature = null;
    /**
     * @var string|null Text content (when type is TEXT).
     */
    private ?string $text = null;
    /**
     * @var File|null File data (when type is FILE).
     */
    private ?File $file = null;
    /**
     * @var FunctionCall|null Function call request (when type is FUNCTION_CALL).
     */
    private ?FunctionCall $functionCall = null;
    /**
     * @var FunctionResponse|null Function response (when type is FUNCTION_RESPONSE).
     */
    private ?FunctionResponse $functionResponse = null;
    /**
     * Constructor that accepts various content types and infers the message part type.
     *
     * @since 0.1.0
     *
     * @param mixed $content The content of this message part.
     * @param MessagePartChannelEnum|null $channel The channel this part belongs to. Defaults to CONTENT.
     * @param string|null $thoughtSignature Optional thought signature for extended thinking.
     * @throws InvalidArgumentException If an unsupported content type is provided.
     */
    public function __construct($content, ?MessagePartChannelEnum $channel = null, ?string $thoughtSignature = null)
    {
        $this->channel = $channel ?? MessagePartChannelEnum::content();
        $this->thoughtSignature = $thoughtSignature;
        if (is_string($content)) {
            $this->type = MessagePartTypeEnum::text();
            $this->text = $content;
        } elseif ($content instanceof File) {
            $this->type = MessagePartTypeEnum::file();
            $this->file = $content;
        } elseif ($content instanceof FunctionCall) {
            $this->type = MessagePartTypeEnum::functionCall();
            $this->functionCall = $content;
        } elseif ($content instanceof FunctionResponse) {
            $this->type = MessagePartTypeEnum::functionResponse();
            $this->functionResponse = $content;
        } else {
            $type = is_object($content) ? get_class($content) : gettype($content);
            throw new InvalidArgumentException(sprintf('Unsupported content type %s. Expected string, File, ' . 'FunctionCall, or FunctionResponse.', $type));
        }
    }
    /**
     * Gets the channel this message part belongs to.
     *
     * @since 0.1.0
     *
     * @return MessagePartChannelEnum The channel.
     */
    public function getChannel(): MessagePartChannelEnum
    {
        return $this->channel;
    }
    /**
     * Gets the type of this message part.
     *
     * @since 0.1.0
     *
     * @return MessagePartTypeEnum The type.
     */
    public function getType(): MessagePartTypeEnum
    {
        return $this->type;
    }
    /**
     * Gets the thought signature.
     *
     * @since 1.3.0
     *
     * @return string|null The thought signature or null if not set.
     */
    public function getThoughtSignature(): ?string
    {
        return $this->thoughtSignature;
    }
    /**
     * Gets the text content.
     *
     * @since 0.1.0
     *
     * @return string|null The text content or null if not a text part.
     */
    public function getText(): ?string
    {
        return $this->text;
    }
    /**
     * Gets the file.
     *
     * @since 0.1.0
     *
     * @return File|null The file or null if not a file part.
     */
    public function getFile(): ?File
    {
        return $this->file;
    }
    /**
     * Gets the function call.
     *
     * @since 0.1.0
     *
     * @return FunctionCall|null The function call or null if not a function call part.
     */
    public function getFunctionCall(): ?FunctionCall
    {
        return $this->functionCall;
    }
    /**
     * Gets the function response.
     *
     * @since 0.1.0
     *
     * @return FunctionResponse|null The function response or null if not a function response part.
     */
    public function getFunctionResponse(): ?FunctionResponse
    {
        return $this->functionResponse;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function getJsonSchema(): array
    {
        $channelSchema = ['type' => 'string', 'enum' => MessagePartChannelEnum::getValues(), 'description' => 'The channel this message part belongs to.'];
        $thoughtSignatureSchema = ['type' => 'string', 'description' => 'Thought signature for extended thinking.'];
        return ['oneOf' => [['type' => 'object', 'properties' => [self::KEY_CHANNEL => $channelSchema, self::KEY_TYPE => ['type' => 'string', 'const' => MessagePartTypeEnum::text()->value], self::KEY_TEXT => ['type' => 'string', 'description' => 'Text content.'], self::KEY_THOUGHT_SIGNATURE => $thoughtSignatureSchema], 'required' => [self::KEY_TYPE, self::KEY_TEXT], 'additionalProperties' => \false], ['type' => 'object', 'properties' => [self::KEY_CHANNEL => $channelSchema, self::KEY_TYPE => ['type' => 'string', 'const' => MessagePartTypeEnum::file()->value], self::KEY_FILE => File::getJsonSchema(), self::KEY_THOUGHT_SIGNATURE => $thoughtSignatureSchema], 'required' => [self::KEY_TYPE, self::KEY_FILE], 'additionalProperties' => \false], ['type' => 'object', 'properties' => [self::KEY_CHANNEL => $channelSchema, self::KEY_TYPE => ['type' => 'string', 'const' => MessagePartTypeEnum::functionCall()->value], self::KEY_FUNCTION_CALL => FunctionCall::getJsonSchema(), self::KEY_THOUGHT_SIGNATURE => $thoughtSignatureSchema], 'required' => [self::KEY_TYPE, self::KEY_FUNCTION_CALL], 'additionalProperties' => \false], ['type' => 'object', 'properties' => [self::KEY_CHANNEL => $channelSchema, self::KEY_TYPE => ['type' => 'string', 'const' => MessagePartTypeEnum::functionResponse()->value], self::KEY_FUNCTION_RESPONSE => FunctionResponse::getJsonSchema(), self::KEY_THOUGHT_SIGNATURE => $thoughtSignatureSchema], 'required' => [self::KEY_TYPE, self::KEY_FUNCTION_RESPONSE], 'additionalProperties' => \false]]];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     *
     * @return MessagePartArrayShape
     */
    public function toArray(): array
    {
        $data = [self::KEY_CHANNEL => $this->channel->value, self::KEY_TYPE => $this->type->value];
        if ($this->text !== null) {
            $data[self::KEY_TEXT] = $this->text;
        } elseif ($this->file !== null) {
            $data[self::KEY_FILE] = $this->file->toArray();
        } elseif ($this->functionCall !== null) {
            $data[self::KEY_FUNCTION_CALL] = $this->functionCall->toArray();
        } elseif ($this->functionResponse !== null) {
            $data[self::KEY_FUNCTION_RESPONSE] = $this->functionResponse->toArray();
        } else {
            throw new RuntimeException('MessagePart requires one of: text, file, functionCall, or functionResponse. ' . 'This should not be a possible condition.');
        }
        if ($this->thoughtSignature !== null) {
            $data[self::KEY_THOUGHT_SIGNATURE] = $this->thoughtSignature;
        }
        return $data;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function fromArray(array $array): self
    {
        if (isset($array[self::KEY_CHANNEL])) {
            $channel = MessagePartChannelEnum::from($array[self::KEY_CHANNEL]);
        } else {
            $channel = null;
        }
        $thoughtSignature = $array[self::KEY_THOUGHT_SIGNATURE] ?? null;
        // Check which properties are set to determine how to construct the MessagePart
        if (isset($array[self::KEY_TEXT])) {
            return new self($array[self::KEY_TEXT], $channel, $thoughtSignature);
        } elseif (isset($array[self::KEY_FILE])) {
            return new self(File::fromArray($array[self::KEY_FILE]), $channel, $thoughtSignature);
        } elseif (isset($array[self::KEY_FUNCTION_CALL])) {
            return new self(FunctionCall::fromArray($array[self::KEY_FUNCTION_CALL]), $channel, $thoughtSignature);
        } elseif (isset($array[self::KEY_FUNCTION_RESPONSE])) {
            return new self(FunctionResponse::fromArray($array[self::KEY_FUNCTION_RESPONSE]), $channel, $thoughtSignature);
        } else {
            throw new InvalidArgumentException('MessagePart requires one of: text, file, functionCall, or functionResponse.');
        }
    }
    /**
     * Performs a deep clone of the message part.
     *
     * This method ensures that nested objects (file, function call, function response)
     * are cloned to prevent modifications to the cloned part from affecting the original.
     *
     * @since 0.4.2
     */
    public function __clone()
    {
        if ($this->file !== null) {
            $this->file = clone $this->file;
        }
        if ($this->functionCall !== null) {
            $this->functionCall = clone $this->functionCall;
        }
        if ($this->functionResponse !== null) {
            $this->functionResponse = clone $this->functionResponse;
        }
    }
}
PK�e]hq���"Messages/Enums/MessageRoleEnum.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Messages\Enums;

use WordPress\AiClient\Common\AbstractEnum;
/**
 * Enum for message roles in AI conversations.
 *
 * @since 0.1.0
 *
 * @method static self user() Creates an instance for USER role.
 * @method static self model() Creates an instance for MODEL role.
 * @method bool isUser() Checks if the role is USER.
 * @method bool isModel() Checks if the role is MODEL.
 */
class MessageRoleEnum extends AbstractEnum
{
    /**
     * User role - messages from the user.
     */
    public const USER = 'user';
    /**
     * Model role - messages from the AI model.
     */
    public const MODEL = 'model';
}
PK�e];-yy&Messages/Enums/MessagePartTypeEnum.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Messages\Enums;

use WordPress\AiClient\Common\AbstractEnum;
/**
 * Enum for message part types.
 *
 * @since 0.1.0
 *
 * @method static self text() Creates an instance for TEXT type.
 * @method static self file() Creates an instance for FILE type.
 * @method static self functionCall() Creates an instance for FUNCTION_CALL type.
 * @method static self functionResponse() Creates an instance for FUNCTION_RESPONSE type.
 * @method bool isText() Checks if the type is TEXT.
 * @method bool isFile() Checks if the type is FILE.
 * @method bool isFunctionCall() Checks if the type is FUNCTION_CALL.
 * @method bool isFunctionResponse() Checks if the type is FUNCTION_RESPONSE.
 */
class MessagePartTypeEnum extends AbstractEnum
{
    /**
     * Text content.
     */
    public const TEXT = 'text';
    /**
     * File content (inline or remote).
     */
    public const FILE = 'file';
    /**
     * Function call request.
     */
    public const FUNCTION_CALL = 'function_call';
    /**
     * Function response.
     */
    public const FUNCTION_RESPONSE = 'function_response';
}
PK�e]0.Ǿ��)Messages/Enums/MessagePartChannelEnum.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Messages\Enums;

use WordPress\AiClient\Common\AbstractEnum;
/**
 * Enum for message part channels.
 *
 * @since 0.1.0
 *
 * @method static self content() Creates an instance for CONTENT channel.
 * @method static self thought() Creates an instance for THOUGHT channel.
 * @method bool isContent() Checks if the channel is CONTENT.
 * @method bool isThought() Checks if the channel is THOUGHT.
 */
class MessagePartChannelEnum extends AbstractEnum
{
    /**
     * Regular (primary) content.
     */
    public const CONTENT = 'content';
    /**
     * Model thinking or reasoning.
     */
    public const THOUGHT = 'thought';
}
PK�e]�HMessages/Enums/ModalityEnum.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Messages\Enums;

use WordPress\AiClient\Common\AbstractEnum;
/**
 * Enum for input/output modalities.
 *
 * @since 0.1.0
 *
 * @method static self text() Creates an instance for TEXT modality.
 * @method static self document() Creates an instance for DOCUMENT modality.
 * @method static self image() Creates an instance for IMAGE modality.
 * @method static self audio() Creates an instance for AUDIO modality.
 * @method static self video() Creates an instance for VIDEO modality.
 * @method bool isText() Checks if the modality is TEXT.
 * @method bool isDocument() Checks if the modality is DOCUMENT.
 * @method bool isImage() Checks if the modality is IMAGE.
 * @method bool isAudio() Checks if the modality is AUDIO.
 * @method bool isVideo() Checks if the modality is VIDEO.
 */
class ModalityEnum extends AbstractEnum
{
    /**
     * Text modality.
     */
    public const TEXT = 'text';
    /**
     * Document modality (PDFs, Word docs, etc.).
     */
    public const DOCUMENT = 'document';
    /**
     * Image modality.
     */
    public const IMAGE = 'image';
    /**
     * Audio modality.
     */
    public const AUDIO = 'audio';
    /**
     * Video modality.
     */
    public const VIDEO = 'video';
}
PK�e]�����C�CAiClient.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient;

use WordPress\AiClientDependencies\Psr\EventDispatcher\EventDispatcherInterface;
use WordPress\AiClientDependencies\Psr\SimpleCache\CacheInterface;
use WordPress\AiClient\Builders\PromptBuilder;
use WordPress\AiClient\Common\Exception\InvalidArgumentException;
use WordPress\AiClient\Common\Exception\RuntimeException;
use WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface;
use WordPress\AiClient\Providers\Contracts\ProviderInterface;
use WordPress\AiClient\Providers\Models\Contracts\ModelInterface;
use WordPress\AiClient\Providers\Models\DTO\ModelConfig;
use WordPress\AiClient\Providers\ProviderRegistry;
use WordPress\AiClient\Results\DTO\GenerativeAiResult;
/**
 * Main AI Client class providing both fluent and traditional APIs for AI operations.
 *
 * This class serves as the primary entry point for AI operations, offering:
 * - Fluent API for easy-to-read chained method calls
 * - Traditional API for array-based configuration (WordPress style)
 * - Integration with provider registry for model discovery
 * - Support for three model specification approaches
 *
 * All model requirements analysis and capability matching is handled
 * automatically by the PromptBuilder, which provides intelligent model
 * discovery based on prompt content and configuration.
 *
 * ## Model Specification Approaches
 *
 * ### 1. Specific Model Instance
 * Use a specific ModelInterface instance when you know exactly which model to use:
 * ```php
 * $model = $registry->getProvider('openai')->getModel('gpt-4');
 * $result = AiClient::generateTextResult('What is PHP?', $model);
 * ```
 *
 * ### 2. ModelConfig for Auto-Discovery
 * Use ModelConfig to specify requirements and let the system discover the best model:
 * ```php
 * $config = new ModelConfig();
 * $config->setTemperature(0.7);
 * $config->setMaxTokens(150);
 *
 * $result = AiClient::generateTextResult('What is PHP?', $config);
 * ```
 *
 * ### 3. Automatic Discovery (Default)
 * Pass null or omit the parameter for intelligent model discovery based on prompt content:
 * ```php
 * // System analyzes prompt and selects appropriate model automatically
 * $result = AiClient::generateTextResult('What is PHP?');
 * $imageResult = AiClient::generateImageResult('A sunset over mountains');
 * ```
 *
 * ## Fluent API Examples
 * ```php
 * // Fluent API with automatic model discovery
 * $result = AiClient::prompt('Generate an image of a sunset')
 *     ->usingTemperature(0.7)
 *     ->generateImageResult();
 *
 * // Fluent API with specific model
 * $result = AiClient::prompt('What is PHP?')
 *     ->usingModel($specificModel)
 *     ->usingTemperature(0.5)
 *     ->generateTextResult();
 *
 * // Fluent API with model configuration
 * $result = AiClient::prompt('Explain quantum physics')
 *     ->usingModelConfig($config)
 *     ->generateTextResult();
 * ```
 *
 * @since 0.1.0
 *
 * @phpstan-import-type Prompt from PromptBuilder
 *
 * phpcs:ignore Generic.Files.LineLength.TooLong
 */
class AiClient
{
    /**
     * @var string The version of the AI Client.
     */
    public const VERSION = '1.3.1';
    /**
     * @var ProviderRegistry|null The default provider registry instance.
     */
    private static ?ProviderRegistry $defaultRegistry = null;
    /**
     * @var EventDispatcherInterface|null The event dispatcher for prompt lifecycle events.
     */
    private static ?EventDispatcherInterface $eventDispatcher = null;
    /**
     * @var CacheInterface|null The PSR-16 cache for storing and retrieving cached data.
     */
    private static ?CacheInterface $cache = null;
    /**
     * Gets the default provider registry instance.
     *
     * @since 0.1.0
     *
     * @return ProviderRegistry The default provider registry.
     */
    public static function defaultRegistry(): ProviderRegistry
    {
        if (self::$defaultRegistry === null) {
            self::$defaultRegistry = new ProviderRegistry();
        }
        return self::$defaultRegistry;
    }
    /**
     * Sets the event dispatcher for prompt lifecycle events.
     *
     * The event dispatcher will be used to dispatch BeforeGenerateResultEvent and
     * AfterGenerateResultEvent during prompt generation.
     *
     * @since 0.4.0
     *
     * @param EventDispatcherInterface|null $dispatcher The event dispatcher, or null to disable.
     * @return void
     */
    public static function setEventDispatcher(?EventDispatcherInterface $dispatcher): void
    {
        self::$eventDispatcher = $dispatcher;
    }
    /**
     * Gets the event dispatcher for prompt lifecycle events.
     *
     * @since 0.4.0
     *
     * @return EventDispatcherInterface|null The event dispatcher, or null if not set.
     */
    public static function getEventDispatcher(): ?EventDispatcherInterface
    {
        return self::$eventDispatcher;
    }
    /**
     * Sets the PSR-16 cache for storing and retrieving cached data.
     *
     * The cache can be used to store AI responses and other data to avoid
     * redundant API calls and improve performance.
     *
     * @since 0.4.0
     *
     * @param CacheInterface|null $cache The PSR-16 cache instance, or null to disable caching.
     * @return void
     */
    public static function setCache(?CacheInterface $cache): void
    {
        self::$cache = $cache;
    }
    /**
     * Gets the PSR-16 cache instance.
     *
     * @since 0.4.0
     *
     * @return CacheInterface|null The cache instance, or null if not set.
     */
    public static function getCache(): ?CacheInterface
    {
        return self::$cache;
    }
    /**
     * Checks if a provider is configured and available for use.
     *
     * Supports multiple input formats for developer convenience:
     * - ProviderAvailabilityInterface: Direct availability check
     * - string (provider ID): e.g., AiClient::isConfigured('openai')
     * - string (class name): e.g., AiClient::isConfigured(OpenAiProvider::class)
     *
     * When using string input, this method leverages the ProviderRegistry's centralized
     * dependency management, ensuring HttpTransporter and authentication are properly
     * injected into availability instances.
     *
     * @since 0.1.0
     * @since 0.2.0 Now supports being passed a provider ID or class name.
     *
     * @param ProviderAvailabilityInterface|string|class-string<ProviderInterface> $availabilityOrIdOrClassName
     *        The provider availability instance, provider ID, or provider class name.
     * @return bool True if the provider is configured and available, false otherwise.
     */
    public static function isConfigured($availabilityOrIdOrClassName): bool
    {
        // Handle direct ProviderAvailabilityInterface (backward compatibility)
        if ($availabilityOrIdOrClassName instanceof ProviderAvailabilityInterface) {
            return $availabilityOrIdOrClassName->isConfigured();
        }
        // Handle string input (provider ID or class name) via registry
        if (is_string($availabilityOrIdOrClassName)) {
            return self::defaultRegistry()->isProviderConfigured($availabilityOrIdOrClassName);
        }
        throw new \InvalidArgumentException('Parameter must be a ProviderAvailabilityInterface instance, provider ID string, or provider class name. ' . sprintf('Received: %s', is_object($availabilityOrIdOrClassName) ? get_class($availabilityOrIdOrClassName) : gettype($availabilityOrIdOrClassName)));
    }
    /**
     * Creates a new prompt builder for fluent API usage.
     *
     * Returns a PromptBuilder instance configured with the specified or default registry.
     * The traditional API methods in this class delegate to PromptBuilder
     * for all generation logic.
     *
     * @since 0.1.0
     *
     * @param Prompt $prompt Optional initial prompt content.
     * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default.
     * @return PromptBuilder The prompt builder instance.
     */
    public static function prompt($prompt = null, ?ProviderRegistry $registry = null): PromptBuilder
    {
        return new PromptBuilder($registry ?? self::defaultRegistry(), $prompt, self::$eventDispatcher);
    }
    /**
     * Generates content using a unified API that automatically detects model capabilities.
     *
     * When no model is provided, this method delegates to PromptBuilder for intelligent
     * model discovery based on prompt content and configuration. When a model is provided,
     * it infers the capability from the model's interfaces and delegates to the capability-based method.
     *
     * @since 0.1.0
     *
     * @param Prompt $prompt The prompt content.
     * @param ModelInterface|ModelConfig $modelOrConfig Specific model to use, or model configuration
     *                                                  for auto-discovery.
     * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default.
     * @return GenerativeAiResult The generation result.
     *
     * @throws \InvalidArgumentException If the provided model doesn't support any known generation type.
     * @throws \RuntimeException If no suitable model can be found for the prompt.
     */
    public static function generateResult($prompt, $modelOrConfig, ?ProviderRegistry $registry = null): GenerativeAiResult
    {
        self::validateModelOrConfigParameter($modelOrConfig);
        return self::getConfiguredPromptBuilder($prompt, $modelOrConfig, $registry)->generateResult();
    }
    /**
     * Generates text using the traditional API approach.
     *
     * @since 0.1.0
     *
     * @param Prompt $prompt The prompt content.
     * @param ModelInterface|ModelConfig|null $modelOrConfig Optional specific model to use,
     *                                                        or model configuration for auto-discovery,
     *                                                        or null for defaults.
     * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default.
     * @return GenerativeAiResult The generation result.
     *
     * @throws \InvalidArgumentException If the prompt format is invalid.
     * @throws \RuntimeException If no suitable model is found.
     */
    public static function generateTextResult($prompt, $modelOrConfig = null, ?ProviderRegistry $registry = null): GenerativeAiResult
    {
        self::validateModelOrConfigParameter($modelOrConfig);
        return self::getConfiguredPromptBuilder($prompt, $modelOrConfig, $registry)->generateTextResult();
    }
    /**
     * Generates an image using the traditional API approach.
     *
     * @since 0.1.0
     *
     * @param Prompt $prompt The prompt content.
     * @param ModelInterface|ModelConfig|null $modelOrConfig Optional specific model to use,
     *                                                        or model configuration for auto-discovery,
     *                                                        or null for defaults.
     * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default.
     * @return GenerativeAiResult The generation result.
     *
     * @throws \InvalidArgumentException If the prompt format is invalid.
     * @throws \RuntimeException If no suitable model is found.
     */
    public static function generateImageResult($prompt, $modelOrConfig = null, ?ProviderRegistry $registry = null): GenerativeAiResult
    {
        self::validateModelOrConfigParameter($modelOrConfig);
        return self::getConfiguredPromptBuilder($prompt, $modelOrConfig, $registry)->generateImageResult();
    }
    /**
     * Converts text to speech using the traditional API approach.
     *
     * @since 0.1.0
     *
     * @param Prompt $prompt The prompt content.
     * @param ModelInterface|ModelConfig|null $modelOrConfig Optional specific model to use,
     *                                                        or model configuration for auto-discovery,
     *                                                        or null for defaults.
     * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default.
     * @return GenerativeAiResult The generation result.
     *
     * @throws \InvalidArgumentException If the prompt format is invalid.
     * @throws \RuntimeException If no suitable model is found.
     */
    public static function convertTextToSpeechResult($prompt, $modelOrConfig = null, ?ProviderRegistry $registry = null): GenerativeAiResult
    {
        self::validateModelOrConfigParameter($modelOrConfig);
        return self::getConfiguredPromptBuilder($prompt, $modelOrConfig, $registry)->convertTextToSpeechResult();
    }
    /**
     * Generates speech using the traditional API approach.
     *
     * @since 0.1.0
     *
     * @param Prompt $prompt The prompt content.
     * @param ModelInterface|ModelConfig|null $modelOrConfig Optional specific model to use,
     *                                                        or model configuration for auto-discovery,
     *                                                        or null for defaults.
     * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default.
     * @return GenerativeAiResult The generation result.
     *
     * @throws \InvalidArgumentException If the prompt format is invalid.
     * @throws \RuntimeException If no suitable model is found.
     */
    public static function generateSpeechResult($prompt, $modelOrConfig = null, ?ProviderRegistry $registry = null): GenerativeAiResult
    {
        self::validateModelOrConfigParameter($modelOrConfig);
        return self::getConfiguredPromptBuilder($prompt, $modelOrConfig, $registry)->generateSpeechResult();
    }
    /**
     * Generates a video using the traditional API approach.
     *
     * @since 1.3.0
     *
     * @param Prompt $prompt The prompt content.
     * @param ModelInterface|ModelConfig|null $modelOrConfig Optional specific model to use,
     *                                                        or model configuration for auto-discovery,
     *                                                        or null for defaults.
     * @param ProviderRegistry|null $registry Optional custom registry. If null, uses default.
     * @return GenerativeAiResult The generation result.
     *
     * @throws \InvalidArgumentException If the prompt format is invalid.
     * @throws \RuntimeException If no suitable model is found.
     */
    public static function generateVideoResult($prompt, $modelOrConfig = null, ?ProviderRegistry $registry = null): GenerativeAiResult
    {
        self::validateModelOrConfigParameter($modelOrConfig);
        return self::getConfiguredPromptBuilder($prompt, $modelOrConfig, $registry)->generateVideoResult();
    }
    /**
     * Creates a new message builder for fluent API usage.
     *
     * This method will be implemented once MessageBuilder is available.
     * MessageBuilder will provide a fluent interface for constructing complex
     * messages with multiple parts, attachments, and metadata.
     *
     * @since 0.1.0
     *
     * @param string|null $text Optional initial message text.
     * @return object MessageBuilder instance (type will be updated when MessageBuilder is available).
     *
     * @throws \RuntimeException When MessageBuilder is not yet available.
     */
    public static function message(?string $text = null)
    {
        throw new RuntimeException('MessageBuilder is not yet available. This method depends on builder infrastructure. ' . 'Use direct generation methods (generateTextResult, generateImageResult, etc.) for now.');
    }
    /**
     * Validates that parameter is ModelInterface, ModelConfig, or null.
     *
     * @param mixed $modelOrConfig The parameter to validate.
     * @return void
     * @throws \InvalidArgumentException If parameter is invalid type.
     */
    private static function validateModelOrConfigParameter($modelOrConfig): void
    {
        if ($modelOrConfig !== null && !$modelOrConfig instanceof ModelInterface && !$modelOrConfig instanceof ModelConfig) {
            throw new InvalidArgumentException('Parameter must be a ModelInterface instance (specific model), ' . 'ModelConfig instance (for auto-discovery), or null (default auto-discovery). ' . sprintf('Received: %s', is_object($modelOrConfig) ? get_class($modelOrConfig) : gettype($modelOrConfig)));
        }
    }
    /**
     * Configures PromptBuilder based on model/config parameter type.
     *
     * @param Prompt $prompt The prompt content.
     * @param ModelInterface|ModelConfig|null $modelOrConfig The model or config parameter.
     * @param ProviderRegistry|null $registry Optional custom registry to use.
     * @return PromptBuilder Configured prompt builder.
     */
    private static function getConfiguredPromptBuilder($prompt, $modelOrConfig, ?ProviderRegistry $registry = null): PromptBuilder
    {
        $builder = self::prompt($prompt, $registry);
        if ($modelOrConfig instanceof ModelInterface) {
            $builder->usingModel($modelOrConfig);
        } elseif ($modelOrConfig instanceof ModelConfig) {
            $builder->usingModelConfig($modelOrConfig);
        }
        // null case: use default model discovery
        return $builder;
    }
}
PK�e]%C��^^Providers/ProviderRegistry.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers;

use WordPress\AiClientDependencies\Http\Discovery\Exception\NotFoundException as DiscoveryNotFoundException;
use WordPress\AiClient\Common\Exception\InvalidArgumentException;
use WordPress\AiClient\Common\Exception\RuntimeException;
use WordPress\AiClient\Providers\Contracts\ProviderInterface;
use WordPress\AiClient\Providers\Contracts\ProviderWithOperationsHandlerInterface;
use WordPress\AiClient\Providers\DTO\ProviderMetadata;
use WordPress\AiClient\Providers\DTO\ProviderModelsMetadata;
use WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface;
use WordPress\AiClient\Providers\Http\Contracts\RequestAuthenticationInterface;
use WordPress\AiClient\Providers\Http\Contracts\WithHttpTransporterInterface;
use WordPress\AiClient\Providers\Http\Contracts\WithRequestAuthenticationInterface;
use WordPress\AiClient\Providers\Http\HttpTransporterFactory;
use WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait;
use WordPress\AiClient\Providers\Models\Contracts\ModelInterface;
use WordPress\AiClient\Providers\Models\DTO\ModelConfig;
use WordPress\AiClient\Providers\Models\DTO\ModelMetadata;
use WordPress\AiClient\Providers\Models\DTO\ModelRequirements;
/**
 * Registry for managing AI providers and their models.
 *
 * This class provides a centralized way to register AI providers, discover
 * their capabilities, and find suitable models based on requirements.
 *
 * @since 0.1.0
 */
class ProviderRegistry implements WithHttpTransporterInterface
{
    use WithHttpTransporterTrait {
        setHttpTransporter as setHttpTransporterOriginal;
    }
    /**
     * @var array<string, class-string<ProviderInterface>> Mapping of provider IDs to class names.
     */
    private array $registeredIdsToClassNames = [];
    /**
     * @var array<class-string<ProviderInterface>, string> Mapping of provider class names to IDs.
     */
    private array $registeredClassNamesToIds = [];
    /**
     * @var array<class-string<ProviderInterface>, RequestAuthenticationInterface> Mapping of provider class names to
     *                                                                             authentication instances.
     */
    private array $providerAuthenticationInstances = [];
    /**
     * Registers a provider class with the registry.
     *
     * @since 0.1.0
     *
     * @param class-string<ProviderInterface> $className The fully qualified provider class name implementing the
     * ProviderInterface
     * @throws InvalidArgumentException If the class doesn't exist or implement the required interface.
     */
    public function registerProvider(string $className): void
    {
        if (!class_exists($className)) {
            throw new InvalidArgumentException(sprintf('Provider class does not exist: %s', $className));
        }
        // Validate that class implements ProviderInterface
        if (!is_subclass_of($className, ProviderInterface::class)) {
            throw new InvalidArgumentException(sprintf('Provider class must implement %s: %s', ProviderInterface::class, $className));
        }
        $metadata = $className::metadata();
        if (!$metadata instanceof ProviderMetadata) {
            throw new InvalidArgumentException(sprintf('Provider must return ProviderMetadata from metadata() method: %s', $className));
        }
        // If there is already a HTTP transporter instance set, hook it up to the provider as needed.
        try {
            $httpTransporter = $this->getHttpTransporter();
        } catch (RuntimeException $e) {
            /*
             * If this fails, it's okay. There is no defined sequence between setting the HTTP transporter in the
             * registry and registering providers in it, so it might be that the transporter is set later. It will be
             * hooked up then.
             * But for now we can ignore this exception and attempt to set the default HTTP transporter, if possible.
             */
            try {
                $this->setHttpTransporter(HttpTransporterFactory::createTransporter());
                $httpTransporter = $this->getHttpTransporter();
            } catch (DiscoveryNotFoundException $e) {
                /*
                 * If no HTTP client implementation can be discovered yet, we can ignore this for now.
                 * It might be set later, so it's not a hard error at this point.
                 * We'll try again the next time a provider is registered, or maybe by that time an explicit
                 * HTTP transporter will have been set.
                 */
            }
        }
        if (isset($httpTransporter)) {
            $this->setHttpTransporterForProvider($className, $httpTransporter);
        }
        // Hook up the request authentication instance, using a default if not set.
        if (!isset($this->providerAuthenticationInstances[$className])) {
            $defaultProviderAuthentication = $this->createDefaultProviderRequestAuthentication($className);
            if ($defaultProviderAuthentication !== null) {
                $this->providerAuthenticationInstances[$className] = $defaultProviderAuthentication;
            }
        }
        if (isset($this->providerAuthenticationInstances[$className])) {
            $this->setRequestAuthenticationForProvider($className, $this->providerAuthenticationInstances[$className]);
        }
        $this->registeredIdsToClassNames[$metadata->getId()] = $className;
        $this->registeredClassNamesToIds[$className] = $metadata->getId();
    }
    /**
     * Gets a list of all registered provider IDs.
     *
     * @since 0.1.0
     *
     * @return list<string> List of registered provider IDs.
     */
    public function getRegisteredProviderIds(): array
    {
        return array_keys($this->registeredIdsToClassNames);
    }
    /**
     * Checks if a provider is registered.
     *
     * @since 0.1.0
     *
     * @param string|class-string<ProviderInterface> $idOrClassName The provider ID or class name to check.
     * @return bool True if the provider is registered.
     */
    public function hasProvider(string $idOrClassName): bool
    {
        return $this->isRegisteredId($idOrClassName) || $this->isRegisteredClassName($idOrClassName);
    }
    /**
     * Gets the class name for a registered provider.
     *
     * @since 0.1.0
     *
     * @param string|class-string<ProviderInterface> $idOrClassName The provider ID or class name.
     * @return class-string<ProviderInterface> The provider class name.
     * @throws InvalidArgumentException If the provider is not registered.
     */
    public function getProviderClassName(string $idOrClassName): string
    {
        // If it's already a class name, return it
        if ($this->isRegisteredClassName($idOrClassName)) {
            return $idOrClassName;
        }
        // If it's a registered ID, return its class name
        if ($this->isRegisteredId($idOrClassName)) {
            return $this->registeredIdsToClassNames[$idOrClassName];
        }
        // Not found
        throw new InvalidArgumentException(sprintf('Provider not registered: %s', $idOrClassName));
    }
    /**
     * Gets the provider ID for a registered provider.
     *
     * @since 0.2.0
     *
     * @param string|class-string<ProviderInterface> $idOrClassName The provider ID or class name.
     * @return string The provider ID.
     * @throws InvalidArgumentException If the provider is not registered.
     */
    public function getProviderId(string $idOrClassName): string
    {
        // If it's already an ID, return it
        if ($this->isRegisteredId($idOrClassName)) {
            return $idOrClassName;
        }
        // If it's a registered class name, return its ID
        if ($this->isRegisteredClassName($idOrClassName)) {
            return $this->registeredClassNamesToIds[$idOrClassName];
        }
        // Not found
        throw new InvalidArgumentException(sprintf('Provider not registered: %s', $idOrClassName));
    }
    /**
     * Checks if a provider is properly configured.
     *
     * @since 0.1.0
     *
     * @param string|class-string<ProviderInterface> $idOrClassName The provider ID or class name.
     * @return bool True if the provider is configured and ready to use.
     */
    public function isProviderConfigured(string $idOrClassName): bool
    {
        try {
            $className = $this->resolveProviderClassName($idOrClassName);
            // Use static method from ProviderInterface
            /** @var class-string<ProviderInterface> $className */
            $availability = $className::availability();
            return $availability->isConfigured();
        } catch (InvalidArgumentException $e) {
            return \false;
        }
    }
    /**
     * Finds models across all available providers that support the given requirements.
     *
     * @since 0.1.0
     *
     * @param ModelRequirements $modelRequirements The requirements to match against.
     * @return list<ProviderModelsMetadata> List of provider models metadata that match requirements.
     */
    public function findModelsMetadataForSupport(ModelRequirements $modelRequirements): array
    {
        $results = [];
        foreach ($this->registeredIdsToClassNames as $providerId => $className) {
            $providerResults = $this->findProviderModelsMetadataForSupport($providerId, $modelRequirements);
            if (!empty($providerResults)) {
                // Use static method from ProviderInterface
                /** @var class-string<ProviderInterface> $className */
                $providerMetadata = $className::metadata();
                $results[] = new ProviderModelsMetadata($providerMetadata, $providerResults);
            }
        }
        return $results;
    }
    /**
     * Finds models within a specific available provider that support the given requirements.
     *
     * @since 0.1.0
     *
     * @param string $idOrClassName The provider ID or class name.
     * @param ModelRequirements $modelRequirements The requirements to match against.
     * @return list<ModelMetadata> List of model metadata that match requirements.
     */
    public function findProviderModelsMetadataForSupport(string $idOrClassName, ModelRequirements $modelRequirements): array
    {
        $className = $this->resolveProviderClassName($idOrClassName);
        // If the provider is not configured, there is no way to use it, so it is considered unavailable.
        if (!$this->isProviderConfigured($className)) {
            return [];
        }
        $modelMetadataDirectory = $className::modelMetadataDirectory();
        // Filter models that meet requirements
        $matchingModels = [];
        foreach ($modelMetadataDirectory->listModelMetadata() as $modelMetadata) {
            if ($modelRequirements->areMetBy($modelMetadata)) {
                $matchingModels[] = $modelMetadata;
            }
        }
        return $matchingModels;
    }
    /**
     * Gets a configured model instance from a provider.
     *
     * @since 0.1.0
     *
     * @param string|class-string<ProviderInterface> $idOrClassName The provider ID or class name.
     * @param string $modelId The model identifier.
     * @param ModelConfig|null $modelConfig The model configuration.
     * @return ModelInterface The configured model instance.
     * @throws InvalidArgumentException If provider or model is not found.
     */
    public function getProviderModel(string $idOrClassName, string $modelId, ?ModelConfig $modelConfig = null): ModelInterface
    {
        $className = $this->resolveProviderClassName($idOrClassName);
        $modelInstance = $className::model($modelId, $modelConfig);
        $this->bindModelDependencies($modelInstance);
        return $modelInstance;
    }
    /**
     * Binds dependencies to a model instance.
     *
     * This method injects required dependencies such as HTTP transporter
     * and authentication into model instances that need them.
     *
     * @since 0.1.0
     *
     * @param ModelInterface $modelInstance The model instance to bind dependencies to.
     * @return void
     */
    public function bindModelDependencies(ModelInterface $modelInstance): void
    {
        $className = $this->resolveProviderClassName($modelInstance->providerMetadata()->getId());
        if ($modelInstance instanceof WithHttpTransporterInterface) {
            $modelInstance->setHttpTransporter($this->getHttpTransporter());
        }
        if ($modelInstance instanceof WithRequestAuthenticationInterface) {
            $requestAuthentication = $this->getProviderRequestAuthentication($className);
            if ($requestAuthentication !== null) {
                $modelInstance->setRequestAuthentication($requestAuthentication);
            }
        }
    }
    /**
     * Gets the class name for a registered provider (handles both ID and class name input).
     *
     * @param string|class-string<ProviderInterface> $idOrClassName The provider ID or class name.
     * @return class-string<ProviderInterface> The provider class name.
     * @throws InvalidArgumentException If provider is not registered.
     */
    private function resolveProviderClassName(string $idOrClassName): string
    {
        // If it's already a class name, return it
        if ($this->isRegisteredClassName($idOrClassName)) {
            return $idOrClassName;
        }
        // If it's a registered ID, return its class name
        if ($this->isRegisteredId($idOrClassName)) {
            return $this->registeredIdsToClassNames[$idOrClassName];
        }
        // Not found
        throw new InvalidArgumentException(sprintf('Provider not registered: %s', $idOrClassName));
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public function setHttpTransporter(HttpTransporterInterface $httpTransporter): void
    {
        $this->setHttpTransporterOriginal($httpTransporter);
        // Make sure all registered providers have the HTTP transporter hooked up as needed.
        foreach ($this->registeredIdsToClassNames as $className) {
            $this->setHttpTransporterForProvider($className, $httpTransporter);
        }
    }
    /**
     * Sets the request authentication instance for the given provider.
     *
     * @since 0.1.0
     *
     * @param string|class-string<ProviderInterface> $idOrClassName The provider ID or class name.
     * @param RequestAuthenticationInterface $requestAuthentication The request authentication instance.
     */
    public function setProviderRequestAuthentication(string $idOrClassName, RequestAuthenticationInterface $requestAuthentication): void
    {
        $className = $this->resolveProviderClassName($idOrClassName);
        $this->providerAuthenticationInstances[$className] = $requestAuthentication;
        $this->setRequestAuthenticationForProvider($className, $requestAuthentication);
    }
    /**
     * Gets the request authentication instance for the given provider, if set.
     *
     * @since 0.1.0
     *
     * @param string|class-string<ProviderInterface> $idOrClassName The provider ID or class name.
     * @return ?RequestAuthenticationInterface The request authentication instance, or null if not set.
     */
    public function getProviderRequestAuthentication(string $idOrClassName): ?RequestAuthenticationInterface
    {
        $className = $this->resolveProviderClassName($idOrClassName);
        if (!isset($this->providerAuthenticationInstances[$className])) {
            return null;
        }
        return $this->providerAuthenticationInstances[$className];
    }
    /**
     * Sets the HTTP transporter for a specific provider, hooking up its class instances.
     *
     * @since 0.1.0
     *
     * @param class-string<ProviderInterface> $className The provider class name.
     * @param HttpTransporterInterface $httpTransporter The HTTP transporter instance.
     */
    private function setHttpTransporterForProvider(string $className, HttpTransporterInterface $httpTransporter): void
    {
        $availability = $className::availability();
        if ($availability instanceof WithHttpTransporterInterface) {
            $availability->setHttpTransporter($httpTransporter);
        }
        $modelMetadataDirectory = $className::modelMetadataDirectory();
        if ($modelMetadataDirectory instanceof WithHttpTransporterInterface) {
            $modelMetadataDirectory->setHttpTransporter($httpTransporter);
        }
        if (is_subclass_of($className, ProviderWithOperationsHandlerInterface::class)) {
            $operationsHandler = $className::operationsHandler();
            if ($operationsHandler instanceof WithHttpTransporterInterface) {
                $operationsHandler->setHttpTransporter($httpTransporter);
            }
        }
    }
    /**
     * Sets the request authentication for a specific provider, hooking up its class instances.
     *
     * @since 0.1.0
     *
     * @param class-string<ProviderInterface> $className The provider class name.
     * @param RequestAuthenticationInterface $requestAuthentication The authentication instance.
     *
     * @throws InvalidArgumentException If the authentication instance is not of the expected type.
     */
    private function setRequestAuthenticationForProvider(string $className, RequestAuthenticationInterface $requestAuthentication): void
    {
        $authenticationMethod = $className::metadata()->getAuthenticationMethod();
        if ($authenticationMethod === null) {
            throw new InvalidArgumentException(sprintf('Provider %s does not expect any authentication, but got %s.', $className, get_class($requestAuthentication)));
        }
        $expectedClass = $authenticationMethod->getImplementationClass();
        if (!$requestAuthentication instanceof $expectedClass) {
            throw new InvalidArgumentException(sprintf('Provider %s expects authentication of type %s, but got %s.', $className, $expectedClass, get_class($requestAuthentication)));
        }
        $availability = $className::availability();
        if ($availability instanceof WithRequestAuthenticationInterface) {
            $availability->setRequestAuthentication($requestAuthentication);
        }
        $modelMetadataDirectory = $className::modelMetadataDirectory();
        if ($modelMetadataDirectory instanceof WithRequestAuthenticationInterface) {
            $modelMetadataDirectory->setRequestAuthentication($requestAuthentication);
        }
        if (is_subclass_of($className, ProviderWithOperationsHandlerInterface::class)) {
            $operationsHandler = $className::operationsHandler();
            if ($operationsHandler instanceof WithRequestAuthenticationInterface) {
                $operationsHandler->setRequestAuthentication($requestAuthentication);
            }
        }
    }
    /**
     * Creates a default request authentication instance for a provider.
     *
     * @since 0.1.0
     *
     * @param class-string<ProviderInterface> $className The provider class name.
     * @return ?RequestAuthenticationInterface The default request authentication instance, or null if not required or
     *                                         if no credential data can be found.
     */
    private function createDefaultProviderRequestAuthentication(string $className): ?RequestAuthenticationInterface
    {
        $providerMetadata = $className::metadata();
        $providerId = $providerMetadata->getId();
        $authenticationMethod = $providerMetadata->getAuthenticationMethod();
        if ($authenticationMethod === null) {
            return null;
        }
        $authenticationClass = $authenticationMethod->getImplementationClass();
        if ($authenticationClass === null) {
            return null;
        }
        $authenticationSchema = $authenticationClass::getJsonSchema();
        // Iterate over all JSON schema object properties to try to determine the necessary authentication data.
        $authenticationData = [];
        if (isset($authenticationSchema['properties']) && is_array($authenticationSchema['properties'])) {
            /** @var array<string, mixed> $details */
            foreach ($authenticationSchema['properties'] as $property => $details) {
                $envVarName = $this->getEnvVarName($providerId, $property);
                // Try to get the value from environment variable or constant.
                $envValue = getenv($envVarName);
                if ($envValue === \false) {
                    if (!defined($envVarName)) {
                        continue;
                        // Skip if neither environment variable nor constant is defined.
                    }
                    $envValue = constant($envVarName);
                    if (!is_scalar($envValue)) {
                        continue;
                    }
                }
                if (isset($details['type'])) {
                    switch ($details['type']) {
                        case 'boolean':
                            $authenticationData[$property] = filter_var($envValue, \FILTER_VALIDATE_BOOLEAN);
                            break;
                        case 'number':
                            $authenticationData[$property] = (int) $envValue;
                            break;
                        case 'string':
                        default:
                            $authenticationData[$property] = (string) $envValue;
                    }
                } else {
                    // Default to string if no type is specified.
                    $authenticationData[$property] = (string) $envValue;
                }
            }
            // If any required fields are missing, return null to avoid immediate errors.
            if (isset($authenticationSchema['required']) && is_array($authenticationSchema['required'])) {
                /** @var list<string> $requiredProperties */
                $requiredProperties = $authenticationSchema['required'];
                if (array_diff_key(array_flip($requiredProperties), $authenticationData)) {
                    return null;
                }
            }
        }
        /** @var RequestAuthenticationInterface */
        /** @var array<string, mixed> $authenticationData */
        return $authenticationClass::fromArray($authenticationData);
    }
    /**
     * Checks if the given value is a registered provider class name.
     *
     * @since 0.4.0
     *
     * @param string $idOrClassName The value to check.
     * @return bool True if it's a registered class name.
     * @phpstan-assert-if-true class-string<ProviderInterface> $idOrClassName
     */
    private function isRegisteredClassName(string $idOrClassName): bool
    {
        return isset($this->registeredClassNamesToIds[$idOrClassName]);
    }
    /**
     * Checks if the given value is a registered provider ID.
     *
     * @since 0.4.0
     *
     * @param string $idOrClassName The value to check.
     * @return bool True if it's a registered provider ID.
     */
    private function isRegisteredId(string $idOrClassName): bool
    {
        return isset($this->registeredIdsToClassNames[$idOrClassName]);
    }
    /**
     * Converts a provider ID and field name to a constant case environment variable name.
     *
     * @since 0.1.0
     *
     * @param string $providerId The provider ID.
     * @param string $field The field name.
     * @return string The environment variable name in CONSTANT_CASE.
     */
    private function getEnvVarName(string $providerId, string $field): string
    {
        // Convert camelCase or kebab-case or snake_case to CONSTANT_CASE.
        $constantCaseProviderId = strtoupper((string) preg_replace('/([a-z])([A-Z])/', '$1_$2', str_replace('-', '_', $providerId)));
        $constantCaseField = strtoupper((string) preg_replace('/([a-z])([A-Z])/', '$1_$2', str_replace('-', '_', $field)));
        return "{$constantCaseProviderId}_{$constantCaseField}";
    }
}
PK�e].�hProviders/AbstractProvider.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers;

use WordPress\AiClient\Providers\Contracts\ModelMetadataDirectoryInterface;
use WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface;
use WordPress\AiClient\Providers\Contracts\ProviderInterface;
use WordPress\AiClient\Providers\DTO\ProviderMetadata;
use WordPress\AiClient\Providers\Models\Contracts\ModelInterface;
use WordPress\AiClient\Providers\Models\DTO\ModelConfig;
use WordPress\AiClient\Providers\Models\DTO\ModelMetadata;
/**
 * Base class for a provider.
 *
 * @since 0.1.0
 */
abstract class AbstractProvider implements ProviderInterface
{
    /**
     * @var array<string, ProviderMetadata> Cache for provider metadata per class.
     */
    private static array $metadataCache = [];
    /**
     * @var array<string, ProviderAvailabilityInterface> Cache for provider availability per class.
     */
    private static array $availabilityCache = [];
    /**
     * @var array<string, ModelMetadataDirectoryInterface> Cache for model metadata directory per class.
     */
    private static array $modelMetadataDirectoryCache = [];
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    final public static function metadata(): ProviderMetadata
    {
        $className = static::class;
        if (!isset(self::$metadataCache[$className])) {
            self::$metadataCache[$className] = static::createProviderMetadata();
        }
        return self::$metadataCache[$className];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    final public static function model(string $modelId, ?ModelConfig $modelConfig = null): ModelInterface
    {
        $providerMetadata = static::metadata();
        $modelMetadata = static::modelMetadataDirectory()->getModelMetadata($modelId);
        $model = static::createModel($modelMetadata, $providerMetadata);
        if ($modelConfig) {
            $model->setConfig($modelConfig);
        }
        return $model;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    final public static function availability(): ProviderAvailabilityInterface
    {
        $className = static::class;
        if (!isset(self::$availabilityCache[$className])) {
            self::$availabilityCache[$className] = static::createProviderAvailability();
        }
        return self::$availabilityCache[$className];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    final public static function modelMetadataDirectory(): ModelMetadataDirectoryInterface
    {
        $className = static::class;
        if (!isset(self::$modelMetadataDirectoryCache[$className])) {
            self::$modelMetadataDirectoryCache[$className] = static::createModelMetadataDirectory();
        }
        return self::$modelMetadataDirectoryCache[$className];
    }
    /**
     * Creates a model instance based on the given model metadata and provider metadata.
     *
     * @since 0.1.0
     *
     * @param ModelMetadata $modelMetadata The model metadata.
     * @param ProviderMetadata $providerMetadata The provider metadata.
     * @return ModelInterface The new model instance.
     */
    abstract protected static function createModel(ModelMetadata $modelMetadata, ProviderMetadata $providerMetadata): ModelInterface;
    /**
     * Creates the provider metadata instance.
     *
     * @since 0.1.0
     *
     * @return ProviderMetadata The provider metadata.
     */
    abstract protected static function createProviderMetadata(): ProviderMetadata;
    /**
     * Creates the provider availability instance.
     *
     * @since 0.1.0
     *
     * @return ProviderAvailabilityInterface The provider availability.
     */
    abstract protected static function createProviderAvailability(): ProviderAvailabilityInterface;
    /**
     * Creates the model metadata directory instance.
     *
     * @since 0.1.0
     *
     * @return ModelMetadataDirectoryInterface The model metadata directory.
     */
    abstract protected static function createModelMetadataDirectory(): ModelMetadataDirectoryInterface;
}
PK�e]�4oX��LProviders/Models/VideoGeneration/Contracts/VideoGenerationModelInterface.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Models\VideoGeneration\Contracts;

use WordPress\AiClient\Messages\DTO\Message;
use WordPress\AiClient\Results\DTO\GenerativeAiResult;
/**
 * Interface for models that support video generation.
 *
 * Provides synchronous methods for generating videos from prompts.
 *
 * @since 1.3.0
 */
interface VideoGenerationModelInterface
{
    /**
     * Generates videos from a prompt.
     *
     * @since 1.3.0
     *
     * @param list<Message> $prompt Array of messages containing the video generation prompt.
     * @return GenerativeAiResult Result containing generated videos.
     */
    public function generateVideoResult(array $prompt): GenerativeAiResult;
}
PK�e]o�qUProviders/Models/VideoGeneration/Contracts/VideoGenerationOperationModelInterface.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Models\VideoGeneration\Contracts;

use WordPress\AiClient\Messages\DTO\Message;
use WordPress\AiClient\Operations\DTO\GenerativeAiOperation;
/**
 * Interface for models that support asynchronous video generation operations.
 *
 * Provides methods for initiating long-running video generation tasks.
 *
 * @since 1.3.0
 */
interface VideoGenerationOperationModelInterface
{
    /**
     * Creates a video generation operation.
     *
     * @since 1.3.0
     *
     * @param list<Message> $prompt Array of messages containing the video generation prompt.
     * @return GenerativeAiOperation The initiated video generation operation.
     */
    public function generateVideoOperation(array $prompt): GenerativeAiOperation;
}
PK�e]����ZProviders/Models/TextToSpeechConversion/Contracts/TextToSpeechConversionModelInterface.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Models\TextToSpeechConversion\Contracts;

use WordPress\AiClient\Messages\DTO\Message;
use WordPress\AiClient\Results\DTO\GenerativeAiResult;
/**
 * Interface for models that support text-to-speech conversion.
 *
 * Provides synchronous methods for converting text to speech audio.
 *
 * @since 0.1.0
 */
interface TextToSpeechConversionModelInterface
{
    /**
     * Converts text to speech.
     *
     * @since 0.1.0
     *
     * @param list<Message> $prompt Array of messages containing the text to convert to speech.
     * @return GenerativeAiResult Result containing generated speech audio.
     */
    public function convertTextToSpeechResult(array $prompt): GenerativeAiResult;
}
PK�e]i�lWWcProviders/Models/TextToSpeechConversion/Contracts/TextToSpeechConversionOperationModelInterface.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Models\TextToSpeechConversion\Contracts;

use WordPress\AiClient\Messages\DTO\Message;
use WordPress\AiClient\Operations\DTO\GenerativeAiOperation;
/**
 * Interface for models that support asynchronous text-to-speech conversion operations.
 *
 * Provides methods for initiating long-running text-to-speech conversion tasks.
 *
 * @since 0.1.0
 */
interface TextToSpeechConversionOperationModelInterface
{
    /**
     * Creates a text-to-speech conversion operation.
     *
     * @since 0.1.0
     *
     * @param list<Message> $prompt Array of messages containing the text to convert to speech.
     * @return GenerativeAiOperation The initiated text-to-speech conversion operation.
     */
    public function convertTextToSpeechOperation(array $prompt): GenerativeAiOperation;
}
PK�e]���4I=I=*Providers/Models/DTO/ModelRequirements.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Models\DTO;

use WordPress\AiClient\Common\AbstractDataTransferObject;
use WordPress\AiClient\Common\Exception\InvalidArgumentException;
use WordPress\AiClient\Messages\DTO\Message;
use WordPress\AiClient\Messages\Enums\ModalityEnum;
use WordPress\AiClient\Providers\Models\Enums\CapabilityEnum;
use WordPress\AiClient\Providers\Models\Enums\OptionEnum;
/**
 * Represents requirements that implementing code has for AI model selection.
 *
 * This class defines the capabilities and options that a model must support
 * in order to be considered suitable for the implementing code's needs.
 *
 * @since 0.1.0
 *
 * @phpstan-import-type RequiredOptionArrayShape from RequiredOption
 *
 * @phpstan-type ModelRequirementsArrayShape array{
 *     requiredCapabilities: list<string>,
 *     requiredOptions: list<RequiredOptionArrayShape>
 * }
 *
 * @extends AbstractDataTransferObject<ModelRequirementsArrayShape>
 */
class ModelRequirements extends AbstractDataTransferObject
{
    public const KEY_REQUIRED_CAPABILITIES = 'requiredCapabilities';
    public const KEY_REQUIRED_OPTIONS = 'requiredOptions';
    /**
     * @var list<CapabilityEnum> The capabilities that the model must support.
     */
    protected array $requiredCapabilities;
    /**
     * @var list<RequiredOption> The options that the model must support with specific values.
     */
    protected array $requiredOptions;
    /**
     * Constructor.
     *
     * @since 0.1.0
     *
     * @param list<CapabilityEnum> $requiredCapabilities The capabilities that the model must support.
     * @param list<RequiredOption> $requiredOptions The options that the model must support with specific values.
     *
     * @throws InvalidArgumentException If arrays are not lists.
     */
    public function __construct(array $requiredCapabilities, array $requiredOptions)
    {
        if (!array_is_list($requiredCapabilities)) {
            throw new InvalidArgumentException('Required capabilities must be a list array.');
        }
        if (!array_is_list($requiredOptions)) {
            throw new InvalidArgumentException('Required options must be a list array.');
        }
        $this->requiredCapabilities = $requiredCapabilities;
        $this->requiredOptions = $requiredOptions;
    }
    /**
     * Gets the capabilities that the model must support.
     *
     * @since 0.1.0
     *
     * @return list<CapabilityEnum> The required capabilities.
     */
    public function getRequiredCapabilities(): array
    {
        return $this->requiredCapabilities;
    }
    /**
     * Gets the options that the model must support with specific values.
     *
     * @since 0.1.0
     *
     * @return list<RequiredOption> The required options.
     */
    public function getRequiredOptions(): array
    {
        return $this->requiredOptions;
    }
    /**
     * Checks whether the given model metadata meets these requirements.
     *
     * @since 0.2.0
     *
     * @param ModelMetadata $metadata The model metadata to check against.
     * @return bool True if the model meets all requirements, false otherwise.
     */
    public function areMetBy(\WordPress\AiClient\Providers\Models\DTO\ModelMetadata $metadata): bool
    {
        // Create lookup maps for better performance (instead of nested foreach loops)
        $capabilitiesMap = [];
        foreach ($metadata->getSupportedCapabilities() as $capability) {
            $capabilitiesMap[$capability->value] = $capability;
        }
        $optionsMap = [];
        foreach ($metadata->getSupportedOptions() as $option) {
            $optionsMap[$option->getName()->value] = $option;
        }
        // Check if all required capabilities are supported using map lookup
        foreach ($this->requiredCapabilities as $requiredCapability) {
            if (!isset($capabilitiesMap[$requiredCapability->value])) {
                return \false;
            }
        }
        // Check if all required options are supported with the specified values
        foreach ($this->requiredOptions as $requiredOption) {
            // Use map lookup instead of linear search
            if (!isset($optionsMap[$requiredOption->getName()->value])) {
                return \false;
            }
            $supportedOption = $optionsMap[$requiredOption->getName()->value];
            // Check if the required value is supported by this option
            if (!$supportedOption->isSupportedValue($requiredOption->getValue())) {
                return \false;
            }
        }
        return \true;
    }
    /**
     * Creates ModelRequirements from prompt data and model configuration.
     *
     * @since 0.2.0
     *
     * @param CapabilityEnum $capability The capability the model must support.
     * @param list<Message> $messages The messages in the conversation.
     * @param ModelConfig $modelConfig The model configuration.
     * @return self The created requirements.
     */
    public static function fromPromptData(CapabilityEnum $capability, array $messages, \WordPress\AiClient\Providers\Models\DTO\ModelConfig $modelConfig): self
    {
        // Start with base capability
        $capabilities = [$capability];
        $inputModalities = [];
        // Check if we have chat history (multiple messages)
        if (count($messages) > 1) {
            $capabilities[] = CapabilityEnum::chatHistory();
        }
        // Analyze all messages to determine required input modalities
        $hasFunctionMessageParts = \false;
        foreach ($messages as $message) {
            foreach ($message->getParts() as $part) {
                // Check for text input
                if ($part->getType()->isText()) {
                    $inputModalities[] = ModalityEnum::text();
                }
                // Check for file inputs
                if ($part->getType()->isFile()) {
                    $file = $part->getFile();
                    if ($file !== null) {
                        if ($file->isImage()) {
                            $inputModalities[] = ModalityEnum::image();
                        } elseif ($file->isAudio()) {
                            $inputModalities[] = ModalityEnum::audio();
                        } elseif ($file->isVideo()) {
                            $inputModalities[] = ModalityEnum::video();
                        } elseif ($file->isDocument() || $file->isText()) {
                            $inputModalities[] = ModalityEnum::document();
                        }
                    }
                }
                // Check for function calls/responses (these might require special capabilities)
                if ($part->getType()->isFunctionCall() || $part->getType()->isFunctionResponse()) {
                    $hasFunctionMessageParts = \true;
                }
            }
        }
        // Convert ModelConfig to RequiredOptions
        $requiredOptions = self::toRequiredOptions($modelConfig);
        // Add additional options based on message analysis
        if ($hasFunctionMessageParts) {
            $requiredOptions = self::includeInRequiredOptions($requiredOptions, new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::functionDeclarations(), \true));
        }
        // Add input modalities if we have any inputs
        if (!empty($inputModalities)) {
            // Remove duplicates
            $inputModalities = array_unique($inputModalities, \SORT_REGULAR);
            $requiredOptions = self::includeInRequiredOptions($requiredOptions, new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::inputModalities(), array_values($inputModalities)));
        }
        // Step 6: Return new ModelRequirements
        return new self($capabilities, $requiredOptions);
    }
    /**
     * Converts ModelConfig to an array of RequiredOptions.
     *
     * @since 0.2.0
     *
     * @param ModelConfig $modelConfig The model configuration.
     * @return list<RequiredOption> The required options.
     */
    private static function toRequiredOptions(\WordPress\AiClient\Providers\Models\DTO\ModelConfig $modelConfig): array
    {
        $requiredOptions = [];
        // Map properties that have corresponding OptionEnum values
        if ($modelConfig->getOutputModalities() !== null) {
            $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::outputModalities(), $modelConfig->getOutputModalities());
        }
        if ($modelConfig->getSystemInstruction() !== null) {
            $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::systemInstruction(), $modelConfig->getSystemInstruction());
        }
        if ($modelConfig->getCandidateCount() !== null) {
            $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::candidateCount(), $modelConfig->getCandidateCount());
        }
        if ($modelConfig->getMaxTokens() !== null) {
            $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::maxTokens(), $modelConfig->getMaxTokens());
        }
        if ($modelConfig->getTemperature() !== null) {
            $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::temperature(), $modelConfig->getTemperature());
        }
        if ($modelConfig->getTopP() !== null) {
            $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::topP(), $modelConfig->getTopP());
        }
        if ($modelConfig->getTopK() !== null) {
            $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::topK(), $modelConfig->getTopK());
        }
        if ($modelConfig->getOutputMimeType() !== null) {
            $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::outputMimeType(), $modelConfig->getOutputMimeType());
        }
        if ($modelConfig->getOutputSchema() !== null) {
            $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::outputSchema(), $modelConfig->getOutputSchema());
        }
        // Handle properties without OptionEnum values as custom options
        if ($modelConfig->getStopSequences() !== null) {
            $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::stopSequences(), $modelConfig->getStopSequences());
        }
        if ($modelConfig->getPresencePenalty() !== null) {
            $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::presencePenalty(), $modelConfig->getPresencePenalty());
        }
        if ($modelConfig->getFrequencyPenalty() !== null) {
            $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::frequencyPenalty(), $modelConfig->getFrequencyPenalty());
        }
        if ($modelConfig->getLogprobs() !== null) {
            $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::logprobs(), $modelConfig->getLogprobs());
        }
        if ($modelConfig->getTopLogprobs() !== null) {
            $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::topLogprobs(), $modelConfig->getTopLogprobs());
        }
        if ($modelConfig->getFunctionDeclarations() !== null) {
            $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::functionDeclarations(), \true);
        }
        if ($modelConfig->getWebSearch() !== null) {
            $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::webSearch(), \true);
        }
        if ($modelConfig->getOutputFileType() !== null) {
            $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::outputFileType(), $modelConfig->getOutputFileType());
        }
        if ($modelConfig->getOutputMediaOrientation() !== null) {
            $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::outputMediaOrientation(), $modelConfig->getOutputMediaOrientation());
        }
        if ($modelConfig->getOutputMediaAspectRatio() !== null) {
            $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::outputMediaAspectRatio(), $modelConfig->getOutputMediaAspectRatio());
        }
        // Add custom options as individual RequiredOptions
        foreach ($modelConfig->getCustomOptions() as $key => $value) {
            $requiredOptions[] = new \WordPress\AiClient\Providers\Models\DTO\RequiredOption(OptionEnum::customOptions(), [$key => $value]);
        }
        return $requiredOptions;
    }
    /**
     * Includes a RequiredOption in the array, ensuring no duplicates based on option name.
     *
     * @since 0.2.0
     *
     * @param list<RequiredOption> $requiredOptions The existing required options.
     * @param RequiredOption $newOption The new option to include.
     * @return list<RequiredOption> The updated required options array.
     */
    private static function includeInRequiredOptions(array $requiredOptions, \WordPress\AiClient\Providers\Models\DTO\RequiredOption $newOption): array
    {
        // Check if we already have this option name
        foreach ($requiredOptions as $index => $existingOption) {
            if ($existingOption->getName()->equals($newOption->getName())) {
                // Replace existing option with new one
                $requiredOptions[$index] = $newOption;
                return $requiredOptions;
            }
        }
        // Option not found, add it
        $requiredOptions[] = $newOption;
        return $requiredOptions;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function getJsonSchema(): array
    {
        return ['type' => 'object', 'properties' => [self::KEY_REQUIRED_CAPABILITIES => ['type' => 'array', 'items' => ['type' => 'string', 'enum' => CapabilityEnum::getValues()], 'description' => 'The capabilities that the model must support.'], self::KEY_REQUIRED_OPTIONS => ['type' => 'array', 'items' => \WordPress\AiClient\Providers\Models\DTO\RequiredOption::getJsonSchema(), 'description' => 'The options that the model must support with specific values.']], 'required' => [self::KEY_REQUIRED_CAPABILITIES, self::KEY_REQUIRED_OPTIONS]];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     *
     * @return ModelRequirementsArrayShape
     */
    public function toArray(): array
    {
        return [self::KEY_REQUIRED_CAPABILITIES => array_map(static fn(CapabilityEnum $capability): string => $capability->value, $this->requiredCapabilities), self::KEY_REQUIRED_OPTIONS => array_map(static fn(\WordPress\AiClient\Providers\Models\DTO\RequiredOption $option): array => $option->toArray(), $this->requiredOptions)];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function fromArray(array $array): self
    {
        static::validateFromArrayData($array, [self::KEY_REQUIRED_CAPABILITIES, self::KEY_REQUIRED_OPTIONS]);
        return new self(array_map(static fn(string $capability): CapabilityEnum => CapabilityEnum::from($capability), $array[self::KEY_REQUIRED_CAPABILITIES]), array_map(static fn(array $optionData): \WordPress\AiClient\Providers\Models\DTO\RequiredOption => \WordPress\AiClient\Providers\Models\DTO\RequiredOption::fromArray($optionData), $array[self::KEY_REQUIRED_OPTIONS]));
    }
}
PK�e]:1�KK'Providers/Models/DTO/RequiredOption.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Models\DTO;

use WordPress\AiClient\Common\AbstractDataTransferObject;
use WordPress\AiClient\Providers\Models\Enums\OptionEnum;
/**
 * Represents an option that the implementing code requires the model to support.
 *
 * This class defines an option that the model must support with a specific value
 * for it to be considered suitable for the implementing code's requirements.
 *
 * @since 0.1.0
 *
 * @phpstan-type RequiredOptionArrayShape array{
 *     name: string,
 *     value: mixed
 * }
 *
 * @extends AbstractDataTransferObject<RequiredOptionArrayShape>
 */
class RequiredOption extends AbstractDataTransferObject
{
    public const KEY_NAME = 'name';
    public const KEY_VALUE = 'value';
    /**
     * @var OptionEnum The option name.
     */
    protected OptionEnum $name;
    /**
     * @var mixed The value that the model must support for this option.
     */
    protected $value;
    /**
     * Constructor.
     *
     * @since 0.1.0
     *
     * @param OptionEnum $name The option name.
     * @param mixed $value The value that the model must support for this option.
     */
    public function __construct(OptionEnum $name, $value)
    {
        $this->name = $name;
        $this->value = $value;
    }
    /**
     * Gets the option name.
     *
     * @since 0.1.0
     *
     * @return OptionEnum The option name.
     */
    public function getName(): OptionEnum
    {
        return $this->name;
    }
    /**
     * Gets the value that the model must support for this option.
     *
     * @since 0.1.0
     *
     * @return mixed The value that the model must support.
     */
    public function getValue()
    {
        return $this->value;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function getJsonSchema(): array
    {
        return ['type' => 'object', 'properties' => [self::KEY_NAME => ['type' => 'string', 'enum' => OptionEnum::getValues(), 'description' => 'The option name.'], self::KEY_VALUE => ['oneOf' => [['type' => 'string'], ['type' => 'number'], ['type' => 'boolean'], ['type' => 'null'], ['type' => 'array'], ['type' => 'object']], 'description' => 'The value that the model must support for this option.']], 'required' => [self::KEY_NAME, self::KEY_VALUE]];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     *
     * @return RequiredOptionArrayShape
     */
    public function toArray(): array
    {
        return [self::KEY_NAME => $this->name->value, self::KEY_VALUE => $this->value];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function fromArray(array $array): self
    {
        static::validateFromArrayData($array, [self::KEY_NAME, self::KEY_VALUE]);
        return new self(OptionEnum::from($array[self::KEY_NAME]), $array[self::KEY_VALUE]);
    }
}
PK�e]f`�		(Providers/Models/DTO/SupportedOption.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Models\DTO;

use WordPress\AiClient\Common\AbstractDataTransferObject;
use WordPress\AiClient\Common\AbstractEnum;
use WordPress\AiClient\Common\Exception\InvalidArgumentException;
use WordPress\AiClient\Providers\Models\Enums\OptionEnum;
/**
 * Represents a supported configuration option for an AI model.
 *
 * This class defines an option that a model supports, including its name
 * and the values that are valid for that option.
 *
 * @since 0.1.0
 *
 * @phpstan-type SupportedOptionArrayShape array{
 *     name: string,
 *     supportedValues?: list<mixed>
 * }
 *
 * @extends AbstractDataTransferObject<SupportedOptionArrayShape>
 */
class SupportedOption extends AbstractDataTransferObject
{
    public const KEY_NAME = 'name';
    public const KEY_SUPPORTED_VALUES = 'supportedValues';
    /**
     * @var OptionEnum The option name.
     */
    protected OptionEnum $name;
    /**
     * @var list<mixed>|null The supported values for this option.
     */
    protected ?array $supportedValues;
    /**
     * Constructor.
     *
     * @since 0.1.0
     *
     * @param OptionEnum $name The option name.
     * @param list<mixed>|null $supportedValues The supported values for this option, or null if any value is supported.
     *
     * @throws InvalidArgumentException If supportedValues is not null and not a list.
     */
    public function __construct(OptionEnum $name, ?array $supportedValues = null)
    {
        if ($supportedValues !== null && !array_is_list($supportedValues)) {
            throw new InvalidArgumentException('Supported values must be a list array.');
        }
        $this->name = $name;
        $this->supportedValues = $supportedValues;
    }
    /**
     * Gets the option name.
     *
     * @since 0.1.0
     *
     * @return OptionEnum The option name.
     */
    public function getName(): OptionEnum
    {
        return $this->name;
    }
    /**
     * Checks if a value is supported for this option.
     *
     * @since 0.1.0
     *
     * @param mixed $value The value to check.
     * @return bool True if the value is supported, false otherwise.
     */
    public function isSupportedValue($value): bool
    {
        // If supportedValues is null, any value is supported
        if ($this->supportedValues === null) {
            return \true;
        }
        // If the value is an array, consider it a set (i.e. order doesn't matter).
        if (is_array($value)) {
            $normalizedValue = self::normalizeArrayForComparison($value);
            foreach ($this->supportedValues as $supportedValue) {
                if (!is_array($supportedValue)) {
                    continue;
                }
                $normalizedSupported = self::normalizeArrayForComparison($supportedValue);
                if ($normalizedValue === $normalizedSupported) {
                    return \true;
                }
            }
            return \false;
        }
        $normalizedValue = self::normalizeValue($value);
        foreach ($this->supportedValues as $supportedValue) {
            if (self::normalizeValue($supportedValue) === $normalizedValue) {
                return \true;
            }
        }
        return \false;
    }
    /**
     * Normalizes an AbstractEnum instance to its string value.
     *
     * This ensures comparisons work correctly even after deserialization
     * (e.g. Redis/Memcached object cache), where AbstractEnum singletons
     * are reconstructed as separate instances.
     *
     * @since 1.2.1
     *
     * @param mixed $value The value to normalize.
     * @return mixed The normalized value.
     */
    private static function normalizeValue($value)
    {
        if ($value instanceof AbstractEnum) {
            return $value->value;
        }
        return $value;
    }
    /**
     * Normalizes and sorts an array for comparison.
     *
     * Maps each element through normalizeValue() and sorts the result,
     * ensuring consistent comparison regardless of element order or
     * AbstractEnum instance identity.
     *
     * @since 1.2.1
     *
     * @param array<mixed> $items The array to normalize.
     * @return array<mixed> The normalized, sorted array.
     */
    private static function normalizeArrayForComparison(array $items): array
    {
        $normalized = array_map([self::class, 'normalizeValue'], $items);
        sort($normalized);
        return $normalized;
    }
    /**
     * Gets the supported values for this option.
     *
     * @since 0.1.0
     *
     * @return list<mixed>|null The supported values, or null if any value is supported.
     */
    public function getSupportedValues(): ?array
    {
        return $this->supportedValues;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function getJsonSchema(): array
    {
        return ['type' => 'object', 'properties' => [self::KEY_NAME => ['type' => 'string', 'enum' => OptionEnum::getValues(), 'description' => 'The option name.'], self::KEY_SUPPORTED_VALUES => ['type' => 'array', 'items' => ['oneOf' => [['type' => 'string'], ['type' => 'number'], ['type' => 'boolean'], ['type' => 'null'], ['type' => 'array'], ['type' => 'object']]], 'description' => 'The supported values for this option.']], 'required' => [self::KEY_NAME]];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     *
     * @return SupportedOptionArrayShape
     */
    public function toArray(): array
    {
        $data = [self::KEY_NAME => $this->name->value];
        if ($this->supportedValues !== null) {
            /** @var list<mixed> $supportedValues */
            $supportedValues = $this->supportedValues;
            $data[self::KEY_SUPPORTED_VALUES] = $supportedValues;
        }
        return $data;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function fromArray(array $array): self
    {
        static::validateFromArrayData($array, [self::KEY_NAME]);
        return new self(OptionEnum::from($array[self::KEY_NAME]), $array[self::KEY_SUPPORTED_VALUES] ?? null);
    }
}
PK�e]�z�
��&Providers/Models/DTO/ModelMetadata.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Models\DTO;

use WordPress\AiClient\Common\AbstractDataTransferObject;
use WordPress\AiClient\Common\Exception\InvalidArgumentException;
use WordPress\AiClient\Providers\Models\Enums\CapabilityEnum;
/**
 * Represents metadata about an AI model.
 *
 * This class contains information about a specific AI model, including
 * its identifier, display name, supported capabilities, and configuration options.
 *
 * @since 0.1.0
 *
 * @phpstan-import-type SupportedOptionArrayShape from SupportedOption
 *
 * @phpstan-type ModelMetadataArrayShape array{
 *     id: string,
 *     name: string,
 *     supportedCapabilities: list<string>,
 *     supportedOptions: list<SupportedOptionArrayShape>
 * }
 *
 * @extends AbstractDataTransferObject<ModelMetadataArrayShape>
 */
class ModelMetadata extends AbstractDataTransferObject
{
    public const KEY_ID = 'id';
    public const KEY_NAME = 'name';
    public const KEY_SUPPORTED_CAPABILITIES = 'supportedCapabilities';
    public const KEY_SUPPORTED_OPTIONS = 'supportedOptions';
    /**
     * @var string The model's unique identifier.
     */
    protected string $id;
    /**
     * @var string The model's display name.
     */
    protected string $name;
    /**
     * @var list<CapabilityEnum> The model's supported capabilities.
     */
    protected array $supportedCapabilities;
    /**
     * @var list<SupportedOption> The model's supported configuration options.
     */
    protected array $supportedOptions;
    /**
     * Constructor.
     *
     * @since 0.1.0
     *
     * @param string $id The model's unique identifier.
     * @param string $name The model's display name.
     * @param list<CapabilityEnum> $supportedCapabilities The model's supported capabilities.
     * @param list<SupportedOption> $supportedOptions The model's supported configuration options.
     *
     * @throws InvalidArgumentException If arrays are not lists.
     */
    public function __construct(string $id, string $name, array $supportedCapabilities, array $supportedOptions)
    {
        if (!array_is_list($supportedCapabilities)) {
            throw new InvalidArgumentException('Supported capabilities must be a list array.');
        }
        if (!array_is_list($supportedOptions)) {
            throw new InvalidArgumentException('Supported options must be a list array.');
        }
        $this->id = $id;
        $this->name = $name;
        $this->supportedCapabilities = $supportedCapabilities;
        $this->supportedOptions = $supportedOptions;
    }
    /**
     * Gets the model's unique identifier.
     *
     * @since 0.1.0
     *
     * @return string The model ID.
     */
    public function getId(): string
    {
        return $this->id;
    }
    /**
     * Gets the model's display name.
     *
     * @since 0.1.0
     *
     * @return string The model name.
     */
    public function getName(): string
    {
        return $this->name;
    }
    /**
     * Gets the model's supported capabilities.
     *
     * @since 0.1.0
     *
     * @return list<CapabilityEnum> The supported capabilities.
     */
    public function getSupportedCapabilities(): array
    {
        return $this->supportedCapabilities;
    }
    /**
     * Gets the model's supported configuration options.
     *
     * @since 0.1.0
     *
     * @return list<SupportedOption> The supported options.
     */
    public function getSupportedOptions(): array
    {
        return $this->supportedOptions;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function getJsonSchema(): array
    {
        return ['type' => 'object', 'properties' => [self::KEY_ID => ['type' => 'string', 'description' => 'The model\'s unique identifier.'], self::KEY_NAME => ['type' => 'string', 'description' => 'The model\'s display name.'], self::KEY_SUPPORTED_CAPABILITIES => ['type' => 'array', 'items' => ['type' => 'string', 'enum' => CapabilityEnum::getValues()], 'description' => 'The model\'s supported capabilities.'], self::KEY_SUPPORTED_OPTIONS => ['type' => 'array', 'items' => \WordPress\AiClient\Providers\Models\DTO\SupportedOption::getJsonSchema(), 'description' => 'The model\'s supported configuration options.']], 'required' => [self::KEY_ID, self::KEY_NAME, self::KEY_SUPPORTED_CAPABILITIES, self::KEY_SUPPORTED_OPTIONS]];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     *
     * @return ModelMetadataArrayShape
     */
    public function toArray(): array
    {
        return [self::KEY_ID => $this->id, self::KEY_NAME => $this->name, self::KEY_SUPPORTED_CAPABILITIES => array_map(static fn(CapabilityEnum $capability): string => $capability->value, $this->supportedCapabilities), self::KEY_SUPPORTED_OPTIONS => array_map(static fn(\WordPress\AiClient\Providers\Models\DTO\SupportedOption $option): array => $option->toArray(), $this->supportedOptions)];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function fromArray(array $array): self
    {
        static::validateFromArrayData($array, [self::KEY_ID, self::KEY_NAME, self::KEY_SUPPORTED_CAPABILITIES, self::KEY_SUPPORTED_OPTIONS]);
        return new self($array[self::KEY_ID], $array[self::KEY_NAME], array_map(static fn(string $capability): CapabilityEnum => CapabilityEnum::from($capability), $array[self::KEY_SUPPORTED_CAPABILITIES]), array_map(static fn(array $optionData): \WordPress\AiClient\Providers\Models\DTO\SupportedOption => \WordPress\AiClient\Providers\Models\DTO\SupportedOption::fromArray($optionData), $array[self::KEY_SUPPORTED_OPTIONS]));
    }
    /**
     * Performs a deep clone of the model metadata.
     *
     * This method ensures that supported option objects are cloned to prevent
     * modifications to the cloned metadata from affecting the original.
     *
     * @since 0.4.2
     */
    public function __clone()
    {
        $clonedOptions = [];
        foreach ($this->supportedOptions as $option) {
            $clonedOptions[] = clone $option;
        }
        $this->supportedOptions = $clonedOptions;
    }
}
PK�e]��f��v�v$Providers/Models/DTO/ModelConfig.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Models\DTO;

use WordPress\AiClient\Common\AbstractDataTransferObject;
use WordPress\AiClient\Common\Exception\InvalidArgumentException;
use WordPress\AiClient\Files\Enums\FileTypeEnum;
use WordPress\AiClient\Files\Enums\MediaOrientationEnum;
use WordPress\AiClient\Messages\Enums\ModalityEnum;
use WordPress\AiClient\Tools\DTO\FunctionDeclaration;
use WordPress\AiClient\Tools\DTO\WebSearch;
/**
 * Represents configuration for an AI model.
 *
 * This class allows configuring various parameters for model behavior,
 * including output modalities, system instructions, generation parameters,
 * and tool integrations.
 *
 * @since 0.1.0
 *
 * @phpstan-import-type FunctionDeclarationArrayShape from FunctionDeclaration
 * @phpstan-import-type WebSearchArrayShape from WebSearch
 *
 * @phpstan-type ModelConfigArrayShape array{
 *     outputModalities?: list<string>,
 *     systemInstruction?: string,
 *     candidateCount?: int,
 *     maxTokens?: int,
 *     temperature?: float,
 *     topP?: float,
 *     topK?: int,
 *     stopSequences?: list<string>,
 *     presencePenalty?: float,
 *     frequencyPenalty?: float,
 *     logprobs?: bool,
 *     topLogprobs?: int,
 *     functionDeclarations?: list<FunctionDeclarationArrayShape>,
 *     webSearch?: WebSearchArrayShape,
 *     outputFileType?: string,
 *     outputMimeType?: string,
 *     outputSchema?: array<string, mixed>,
 *     outputMediaOrientation?: string,
 *     outputMediaAspectRatio?: string,
 *     outputSpeechVoice?: string,
 *     customOptions?: array<string, mixed>
 * }
 *
 * @extends AbstractDataTransferObject<ModelConfigArrayShape>
 */
class ModelConfig extends AbstractDataTransferObject
{
    public const KEY_OUTPUT_MODALITIES = 'outputModalities';
    public const KEY_SYSTEM_INSTRUCTION = 'systemInstruction';
    public const KEY_CANDIDATE_COUNT = 'candidateCount';
    public const KEY_MAX_TOKENS = 'maxTokens';
    public const KEY_TEMPERATURE = 'temperature';
    public const KEY_TOP_P = 'topP';
    public const KEY_TOP_K = 'topK';
    public const KEY_STOP_SEQUENCES = 'stopSequences';
    public const KEY_PRESENCE_PENALTY = 'presencePenalty';
    public const KEY_FREQUENCY_PENALTY = 'frequencyPenalty';
    public const KEY_LOGPROBS = 'logprobs';
    public const KEY_TOP_LOGPROBS = 'topLogprobs';
    public const KEY_FUNCTION_DECLARATIONS = 'functionDeclarations';
    public const KEY_WEB_SEARCH = 'webSearch';
    public const KEY_OUTPUT_FILE_TYPE = 'outputFileType';
    public const KEY_OUTPUT_MIME_TYPE = 'outputMimeType';
    public const KEY_OUTPUT_SCHEMA = 'outputSchema';
    public const KEY_OUTPUT_MEDIA_ORIENTATION = 'outputMediaOrientation';
    public const KEY_OUTPUT_MEDIA_ASPECT_RATIO = 'outputMediaAspectRatio';
    public const KEY_OUTPUT_SPEECH_VOICE = 'outputSpeechVoice';
    public const KEY_CUSTOM_OPTIONS = 'customOptions';
    /*
     * Note: This key is not an actual model config key, but specified here for convenience.
     * It is relevant for model discovery, to determine which models support which input modalities.
     * The actual input modalities are part of the message sent to the model, not the model config.
     */
    public const KEY_INPUT_MODALITIES = 'inputModalities';
    /**
     * @var list<ModalityEnum>|null Output modalities for the model.
     */
    protected ?array $outputModalities = null;
    /**
     * @var string|null System instruction for the model.
     */
    protected ?string $systemInstruction = null;
    /**
     * @var int|null Number of response candidates to generate.
     */
    protected ?int $candidateCount = null;
    /**
     * @var int|null Maximum number of tokens to generate.
     */
    protected ?int $maxTokens = null;
    /**
     * @var float|null Temperature for randomness (0.0 to 2.0).
     */
    protected ?float $temperature = null;
    /**
     * @var float|null Top-p nucleus sampling parameter.
     */
    protected ?float $topP = null;
    /**
     * @var int|null Top-k sampling parameter.
     */
    protected ?int $topK = null;
    /**
     * @var list<string>|null Stop sequences.
     */
    protected ?array $stopSequences = null;
    /**
     * @var float|null Presence penalty for reducing repetition.
     */
    protected ?float $presencePenalty = null;
    /**
     * @var float|null Frequency penalty for reducing repetition.
     */
    protected ?float $frequencyPenalty = null;
    /**
     * @var bool|null Whether to return log probabilities.
     */
    protected ?bool $logprobs = null;
    /**
     * @var int|null Number of top log probabilities to return.
     */
    protected ?int $topLogprobs = null;
    /**
     * @var list<FunctionDeclaration>|null Function declarations available to the model.
     */
    protected ?array $functionDeclarations = null;
    /**
     * @var WebSearch|null Web search configuration for the model.
     */
    protected ?WebSearch $webSearch = null;
    /**
     * @var FileTypeEnum|null Output file type.
     */
    protected ?FileTypeEnum $outputFileType = null;
    /**
     * @var string|null Output MIME type.
     */
    protected ?string $outputMimeType = null;
    /**
     * @var array<string, mixed>|null Output schema (JSON schema).
     */
    protected ?array $outputSchema = null;
    /**
     * @var MediaOrientationEnum|null Output media orientation.
     */
    protected ?MediaOrientationEnum $outputMediaOrientation = null;
    /**
     * @var string|null Output media aspect ratio (e.g. 3:2, 16:9).
     */
    protected ?string $outputMediaAspectRatio = null;
    /**
     * @var string|null Output speech voice.
     */
    protected ?string $outputSpeechVoice = null;
    /**
     * @var array<string, mixed> Custom provider-specific options.
     */
    protected array $customOptions = [];
    /**
     * Creates a deep clone of this configuration.
     *
     * Clones nested objects (functionDeclarations, webSearch) to ensure
     * the cloned configuration is independent of the original.
     * Enum value objects (outputModalities, outputFileType, outputMediaOrientation)
     * are intentionally shared as they are immutable.
     *
     * @since 0.4.2
     */
    public function __clone()
    {
        // Deep clone function declarations if set
        if ($this->functionDeclarations !== null) {
            $clonedDeclarations = [];
            foreach ($this->functionDeclarations as $declaration) {
                $clonedDeclarations[] = clone $declaration;
            }
            $this->functionDeclarations = $clonedDeclarations;
        }
        // Clone web search if set
        if ($this->webSearch !== null) {
            $this->webSearch = clone $this->webSearch;
        }
        // Note: Enum value objects (outputModalities, outputFileType, outputMediaOrientation)
        // are immutable and can be safely shared.
    }
    /**
     * Sets the output modalities.
     *
     * @since 0.1.0
     *
     * @param list<ModalityEnum> $outputModalities The output modalities.
     *
     * @throws InvalidArgumentException If the array is not a list.
     */
    public function setOutputModalities(array $outputModalities): void
    {
        if (!array_is_list($outputModalities)) {
            throw new InvalidArgumentException('Output modalities must be a list array.');
        }
        $this->outputModalities = $outputModalities;
    }
    /**
     * Gets the output modalities.
     *
     * @since 0.1.0
     *
     * @return list<ModalityEnum>|null The output modalities.
     */
    public function getOutputModalities(): ?array
    {
        return $this->outputModalities;
    }
    /**
     * Sets the system instruction.
     *
     * @since 0.1.0
     *
     * @param string $systemInstruction The system instruction.
     */
    public function setSystemInstruction(string $systemInstruction): void
    {
        $this->systemInstruction = $systemInstruction;
    }
    /**
     * Gets the system instruction.
     *
     * @since 0.1.0
     *
     * @return string|null The system instruction.
     */
    public function getSystemInstruction(): ?string
    {
        return $this->systemInstruction;
    }
    /**
     * Sets the candidate count.
     *
     * @since 0.1.0
     *
     * @param int $candidateCount The candidate count.
     */
    public function setCandidateCount(int $candidateCount): void
    {
        $this->candidateCount = $candidateCount;
    }
    /**
     * Gets the candidate count.
     *
     * @since 0.1.0
     *
     * @return int|null The candidate count.
     */
    public function getCandidateCount(): ?int
    {
        return $this->candidateCount;
    }
    /**
     * Sets the maximum tokens.
     *
     * @since 0.1.0
     *
     * @param int $maxTokens The maximum tokens.
     */
    public function setMaxTokens(int $maxTokens): void
    {
        $this->maxTokens = $maxTokens;
    }
    /**
     * Gets the maximum tokens.
     *
     * @since 0.1.0
     *
     * @return int|null The maximum tokens.
     */
    public function getMaxTokens(): ?int
    {
        return $this->maxTokens;
    }
    /**
     * Sets the temperature.
     *
     * @since 0.1.0
     *
     * @param float $temperature The temperature.
     */
    public function setTemperature(float $temperature): void
    {
        $this->temperature = $temperature;
    }
    /**
     * Gets the temperature.
     *
     * @since 0.1.0
     *
     * @return float|null The temperature.
     */
    public function getTemperature(): ?float
    {
        return $this->temperature;
    }
    /**
     * Sets the top-p parameter.
     *
     * @since 0.1.0
     *
     * @param float $topP The top-p parameter.
     */
    public function setTopP(float $topP): void
    {
        $this->topP = $topP;
    }
    /**
     * Gets the top-p parameter.
     *
     * @since 0.1.0
     *
     * @return float|null The top-p parameter.
     */
    public function getTopP(): ?float
    {
        return $this->topP;
    }
    /**
     * Sets the top-k parameter.
     *
     * @since 0.1.0
     *
     * @param int $topK The top-k parameter.
     */
    public function setTopK(int $topK): void
    {
        $this->topK = $topK;
    }
    /**
     * Gets the top-k parameter.
     *
     * @since 0.1.0
     *
     * @return int|null The top-k parameter.
     */
    public function getTopK(): ?int
    {
        return $this->topK;
    }
    /**
     * Sets the stop sequences.
     *
     * @since 0.1.0
     *
     * @param list<string> $stopSequences The stop sequences.
     *
     * @throws InvalidArgumentException If the array is not a list.
     */
    public function setStopSequences(array $stopSequences): void
    {
        if (!array_is_list($stopSequences)) {
            throw new InvalidArgumentException('Stop sequences must be a list array.');
        }
        $this->stopSequences = $stopSequences;
    }
    /**
     * Gets the stop sequences.
     *
     * @since 0.1.0
     *
     * @return list<string>|null The stop sequences.
     */
    public function getStopSequences(): ?array
    {
        return $this->stopSequences;
    }
    /**
     * Sets the presence penalty.
     *
     * @since 0.1.0
     *
     * @param float $presencePenalty The presence penalty.
     */
    public function setPresencePenalty(float $presencePenalty): void
    {
        $this->presencePenalty = $presencePenalty;
    }
    /**
     * Gets the presence penalty.
     *
     * @since 0.1.0
     *
     * @return float|null The presence penalty.
     */
    public function getPresencePenalty(): ?float
    {
        return $this->presencePenalty;
    }
    /**
     * Sets the frequency penalty.
     *
     * @since 0.1.0
     *
     * @param float $frequencyPenalty The frequency penalty.
     */
    public function setFrequencyPenalty(float $frequencyPenalty): void
    {
        $this->frequencyPenalty = $frequencyPenalty;
    }
    /**
     * Gets the frequency penalty.
     *
     * @since 0.1.0
     *
     * @return float|null The frequency penalty.
     */
    public function getFrequencyPenalty(): ?float
    {
        return $this->frequencyPenalty;
    }
    /**
     * Sets whether to return log probabilities.
     *
     * @since 0.1.0
     *
     * @param bool $logprobs Whether to return log probabilities.
     */
    public function setLogprobs(bool $logprobs): void
    {
        $this->logprobs = $logprobs;
    }
    /**
     * Gets whether to return log probabilities.
     *
     * @since 0.1.0
     *
     * @return bool|null Whether to return log probabilities.
     */
    public function getLogprobs(): ?bool
    {
        return $this->logprobs;
    }
    /**
     * Sets the number of top log probabilities to return.
     *
     * @since 0.1.0
     *
     * @param int $topLogprobs The number of top log probabilities.
     */
    public function setTopLogprobs(int $topLogprobs): void
    {
        $this->topLogprobs = $topLogprobs;
    }
    /**
     * Gets the number of top log probabilities to return.
     *
     * @since 0.1.0
     *
     * @return int|null The number of top log probabilities.
     */
    public function getTopLogprobs(): ?int
    {
        return $this->topLogprobs;
    }
    /**
     * Sets the function declarations.
     *
     * @since 0.1.0
     *
     * @param list<FunctionDeclaration> $functionDeclarations The function declarations.
     *
     * @throws InvalidArgumentException If the array is not a list.
     */
    public function setFunctionDeclarations(array $functionDeclarations): void
    {
        if (!array_is_list($functionDeclarations)) {
            throw new InvalidArgumentException('Function declarations must be a list array.');
        }
        $this->functionDeclarations = $functionDeclarations;
    }
    /**
     * Gets the function declarations.
     *
     * @since 0.1.0
     *
     * @return list<FunctionDeclaration>|null The function declarations.
     */
    public function getFunctionDeclarations(): ?array
    {
        return $this->functionDeclarations;
    }
    /**
     * Sets the web search configuration.
     *
     * @since 0.1.0
     *
     * @param WebSearch $webSearch The web search configuration.
     */
    public function setWebSearch(WebSearch $webSearch): void
    {
        $this->webSearch = $webSearch;
    }
    /**
     * Gets the web search configuration.
     *
     * @since 0.1.0
     *
     * @return WebSearch|null The web search configuration.
     */
    public function getWebSearch(): ?WebSearch
    {
        return $this->webSearch;
    }
    /**
     * Sets the output file type.
     *
     * @since 0.1.0
     *
     * @param FileTypeEnum $outputFileType The output file type.
     */
    public function setOutputFileType(FileTypeEnum $outputFileType): void
    {
        $this->outputFileType = $outputFileType;
    }
    /**
     * Gets the output file type.
     *
     * @since 0.1.0
     *
     * @return FileTypeEnum|null The output file type.
     */
    public function getOutputFileType(): ?FileTypeEnum
    {
        return $this->outputFileType;
    }
    /**
     * Sets the output MIME type.
     *
     * @since 0.1.0
     *
     * @param string $outputMimeType The output MIME type.
     */
    public function setOutputMimeType(string $outputMimeType): void
    {
        $this->outputMimeType = $outputMimeType;
    }
    /**
     * Gets the output MIME type.
     *
     * @since 0.1.0
     *
     * @return string|null The output MIME type.
     */
    public function getOutputMimeType(): ?string
    {
        return $this->outputMimeType;
    }
    /**
     * Sets the output schema.
     *
     * When setting an output schema, this method automatically sets
     * the output MIME type to "application/json" if not already set.
     *
     * @since 0.1.0
     *
     * @param array<string, mixed> $outputSchema The output schema (JSON schema).
     */
    public function setOutputSchema(array $outputSchema): void
    {
        $this->outputSchema = $outputSchema;
        // Automatically set outputMimeType to application/json when schema is provided
        if ($this->outputMimeType === null) {
            $this->outputMimeType = 'application/json';
        }
    }
    /**
     * Gets the output schema.
     *
     * @since 0.1.0
     *
     * @return array<string, mixed>|null The output schema.
     */
    public function getOutputSchema(): ?array
    {
        return $this->outputSchema;
    }
    /**
     * Sets the output media orientation.
     *
     * @since 0.1.0
     *
     * @param MediaOrientationEnum $outputMediaOrientation The output media orientation.
     */
    public function setOutputMediaOrientation(MediaOrientationEnum $outputMediaOrientation): void
    {
        if ($this->outputMediaAspectRatio) {
            $this->validateMediaOrientationAspectRatioCompatibility($outputMediaOrientation, $this->outputMediaAspectRatio);
        }
        $this->outputMediaOrientation = $outputMediaOrientation;
    }
    /**
     * Gets the output media orientation.
     *
     * @since 0.1.0
     *
     * @return MediaOrientationEnum|null The output media orientation.
     */
    public function getOutputMediaOrientation(): ?MediaOrientationEnum
    {
        return $this->outputMediaOrientation;
    }
    /**
     * Sets the output media aspect ratio.
     *
     * If set, this supersedes the output media orientation, as it is a more specific configuration.
     *
     * @since 0.1.0
     *
     * @param string $outputMediaAspectRatio The output media aspect ratio (e.g. 3:2, 16:9).
     */
    public function setOutputMediaAspectRatio(string $outputMediaAspectRatio): void
    {
        if (!preg_match('/^\d+:\d+$/', $outputMediaAspectRatio)) {
            throw new InvalidArgumentException('Output media aspect ratio must be in the format "width:height" (e.g. 3:2, 16:9).');
        }
        if ($this->outputMediaOrientation) {
            $this->validateMediaOrientationAspectRatioCompatibility($this->outputMediaOrientation, $outputMediaAspectRatio);
        }
        $this->outputMediaAspectRatio = $outputMediaAspectRatio;
    }
    /**
     * Gets the output media aspect ratio.
     *
     * @since 0.1.0
     *
     * @return string|null The output media aspect ratio (e.g. 3:2, 16:9).
     */
    public function getOutputMediaAspectRatio(): ?string
    {
        return $this->outputMediaAspectRatio;
    }
    /**
     * Validates that the given media orientation and aspect ratio values do not conflict with each other.
     *
     * @since 0.4.0
     *
     * @param MediaOrientationEnum $orientation The desired media orientation.
     * @param string $aspectRatio The desired media aspect ratio.
     */
    protected function validateMediaOrientationAspectRatioCompatibility(MediaOrientationEnum $orientation, string $aspectRatio): void
    {
        $aspectRatioParts = explode(':', $aspectRatio);
        if ($orientation->isSquare() && $aspectRatioParts[0] !== $aspectRatioParts[1]) {
            throw new InvalidArgumentException('The aspect ratio "' . $aspectRatio . '" is not compatible with the square orientation.');
        }
        if ($orientation->isLandscape() && $aspectRatioParts[0] <= $aspectRatioParts[1]) {
            throw new InvalidArgumentException('The aspect ratio "' . $aspectRatio . '" is not compatible with the landscape orientation.');
        }
        if ($orientation->isPortrait() && $aspectRatioParts[0] >= $aspectRatioParts[1]) {
            throw new InvalidArgumentException('The aspect ratio "' . $aspectRatio . '" is not compatible with the portrait orientation.');
        }
    }
    /**
     * Sets the output speech voice.
     *
     * @since 0.1.0
     *
     * @param string $outputSpeechVoice The output speech voice.
     */
    public function setOutputSpeechVoice(string $outputSpeechVoice): void
    {
        $this->outputSpeechVoice = $outputSpeechVoice;
    }
    /**
     * Gets the output speech voice.
     *
     * @since 0.1.0
     *
     * @return string|null The output speech voice.
     */
    public function getOutputSpeechVoice(): ?string
    {
        return $this->outputSpeechVoice;
    }
    /**
     * Sets a single custom option.
     *
     * @since 0.1.0
     *
     * @param string $key   The option key.
     * @param mixed  $value The option value.
     */
    public function setCustomOption(string $key, $value): void
    {
        $this->customOptions[$key] = $value;
    }
    /**
     * Sets the custom options.
     *
     * @since 0.1.0
     *
     * @param array<string, mixed> $customOptions The custom options.
     */
    public function setCustomOptions(array $customOptions): void
    {
        $this->customOptions = $customOptions;
    }
    /**
     * Gets the custom options.
     *
     * @since 0.1.0
     *
     * @return array<string, mixed> The custom options.
     */
    public function getCustomOptions(): array
    {
        return $this->customOptions;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function getJsonSchema(): array
    {
        return ['type' => 'object', 'properties' => [self::KEY_OUTPUT_MODALITIES => ['type' => 'array', 'items' => ['type' => 'string', 'enum' => ModalityEnum::getValues()], 'description' => 'Output modalities for the model.'], self::KEY_SYSTEM_INSTRUCTION => ['type' => 'string', 'description' => 'System instruction for the model.'], self::KEY_CANDIDATE_COUNT => ['type' => 'integer', 'minimum' => 1, 'description' => 'Number of response candidates to generate.'], self::KEY_MAX_TOKENS => ['type' => 'integer', 'minimum' => 1, 'description' => 'Maximum number of tokens to generate.'], self::KEY_TEMPERATURE => ['type' => 'number', 'minimum' => 0.0, 'maximum' => 2.0, 'description' => 'Temperature for randomness.'], self::KEY_TOP_P => ['type' => 'number', 'minimum' => 0.0, 'maximum' => 1.0, 'description' => 'Top-p nucleus sampling parameter.'], self::KEY_TOP_K => ['type' => 'integer', 'minimum' => 1, 'description' => 'Top-k sampling parameter.'], self::KEY_STOP_SEQUENCES => ['type' => 'array', 'items' => ['type' => 'string'], 'description' => 'Stop sequences.'], self::KEY_PRESENCE_PENALTY => ['type' => 'number', 'description' => 'Presence penalty for reducing repetition.'], self::KEY_FREQUENCY_PENALTY => ['type' => 'number', 'description' => 'Frequency penalty for reducing repetition.'], self::KEY_LOGPROBS => ['type' => 'boolean', 'description' => 'Whether to return log probabilities.'], self::KEY_TOP_LOGPROBS => ['type' => 'integer', 'minimum' => 1, 'description' => 'Number of top log probabilities to return.'], self::KEY_FUNCTION_DECLARATIONS => ['type' => 'array', 'items' => FunctionDeclaration::getJsonSchema(), 'description' => 'Function declarations available to the model.'], self::KEY_WEB_SEARCH => WebSearch::getJsonSchema(), self::KEY_OUTPUT_FILE_TYPE => ['type' => 'string', 'enum' => FileTypeEnum::getValues(), 'description' => 'Output file type.'], self::KEY_OUTPUT_MIME_TYPE => ['type' => 'string', 'description' => 'Output MIME type.'], self::KEY_OUTPUT_SCHEMA => ['type' => 'object', 'additionalProperties' => \true, 'description' => 'Output schema (JSON schema).'], self::KEY_OUTPUT_MEDIA_ORIENTATION => ['type' => 'string', 'enum' => MediaOrientationEnum::getValues(), 'description' => 'Output media orientation.'], self::KEY_OUTPUT_MEDIA_ASPECT_RATIO => ['type' => 'string', 'pattern' => '^\d+:\d+$', 'description' => 'Output media aspect ratio.'], self::KEY_OUTPUT_SPEECH_VOICE => ['type' => 'string', 'description' => 'Output speech voice.'], self::KEY_CUSTOM_OPTIONS => ['type' => 'object', 'additionalProperties' => \true, 'description' => 'Custom provider-specific options.']], 'additionalProperties' => \false];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     *
     * @return ModelConfigArrayShape
     */
    public function toArray(): array
    {
        $data = [];
        if ($this->outputModalities !== null) {
            $data[self::KEY_OUTPUT_MODALITIES] = array_map(static function (ModalityEnum $modality): string {
                return $modality->value;
            }, $this->outputModalities);
        }
        if ($this->systemInstruction !== null) {
            $data[self::KEY_SYSTEM_INSTRUCTION] = $this->systemInstruction;
        }
        if ($this->candidateCount !== null) {
            $data[self::KEY_CANDIDATE_COUNT] = $this->candidateCount;
        }
        if ($this->maxTokens !== null) {
            $data[self::KEY_MAX_TOKENS] = $this->maxTokens;
        }
        if ($this->temperature !== null) {
            $data[self::KEY_TEMPERATURE] = $this->temperature;
        }
        if ($this->topP !== null) {
            $data[self::KEY_TOP_P] = $this->topP;
        }
        if ($this->topK !== null) {
            $data[self::KEY_TOP_K] = $this->topK;
        }
        if ($this->stopSequences !== null) {
            $data[self::KEY_STOP_SEQUENCES] = $this->stopSequences;
        }
        if ($this->presencePenalty !== null) {
            $data[self::KEY_PRESENCE_PENALTY] = $this->presencePenalty;
        }
        if ($this->frequencyPenalty !== null) {
            $data[self::KEY_FREQUENCY_PENALTY] = $this->frequencyPenalty;
        }
        if ($this->logprobs !== null) {
            $data[self::KEY_LOGPROBS] = $this->logprobs;
        }
        if ($this->topLogprobs !== null) {
            $data[self::KEY_TOP_LOGPROBS] = $this->topLogprobs;
        }
        if ($this->functionDeclarations !== null) {
            $data[self::KEY_FUNCTION_DECLARATIONS] = array_map(static function (FunctionDeclaration $functionDeclaration): array {
                return $functionDeclaration->toArray();
            }, $this->functionDeclarations);
        }
        if ($this->webSearch !== null) {
            $data[self::KEY_WEB_SEARCH] = $this->webSearch->toArray();
        }
        if ($this->outputFileType !== null) {
            $data[self::KEY_OUTPUT_FILE_TYPE] = $this->outputFileType->value;
        }
        if ($this->outputMimeType !== null) {
            $data[self::KEY_OUTPUT_MIME_TYPE] = $this->outputMimeType;
        }
        if ($this->outputSchema !== null) {
            $data[self::KEY_OUTPUT_SCHEMA] = $this->outputSchema;
        }
        if ($this->outputMediaOrientation !== null) {
            $data[self::KEY_OUTPUT_MEDIA_ORIENTATION] = $this->outputMediaOrientation->value;
        }
        if ($this->outputMediaAspectRatio !== null) {
            $data[self::KEY_OUTPUT_MEDIA_ASPECT_RATIO] = $this->outputMediaAspectRatio;
        }
        if ($this->outputSpeechVoice !== null) {
            $data[self::KEY_OUTPUT_SPEECH_VOICE] = $this->outputSpeechVoice;
        }
        if (!empty($this->customOptions)) {
            $data[self::KEY_CUSTOM_OPTIONS] = $this->customOptions;
        }
        return $data;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function fromArray(array $array): self
    {
        $config = new self();
        if (isset($array[self::KEY_OUTPUT_MODALITIES])) {
            $config->setOutputModalities(array_map(static fn(string $modality): ModalityEnum => ModalityEnum::from($modality), $array[self::KEY_OUTPUT_MODALITIES]));
        }
        if (isset($array[self::KEY_SYSTEM_INSTRUCTION])) {
            $config->setSystemInstruction($array[self::KEY_SYSTEM_INSTRUCTION]);
        }
        if (isset($array[self::KEY_CANDIDATE_COUNT])) {
            $config->setCandidateCount($array[self::KEY_CANDIDATE_COUNT]);
        }
        if (isset($array[self::KEY_MAX_TOKENS])) {
            $config->setMaxTokens($array[self::KEY_MAX_TOKENS]);
        }
        if (isset($array[self::KEY_TEMPERATURE])) {
            $config->setTemperature($array[self::KEY_TEMPERATURE]);
        }
        if (isset($array[self::KEY_TOP_P])) {
            $config->setTopP($array[self::KEY_TOP_P]);
        }
        if (isset($array[self::KEY_TOP_K])) {
            $config->setTopK($array[self::KEY_TOP_K]);
        }
        if (isset($array[self::KEY_STOP_SEQUENCES])) {
            $config->setStopSequences($array[self::KEY_STOP_SEQUENCES]);
        }
        if (isset($array[self::KEY_PRESENCE_PENALTY])) {
            $config->setPresencePenalty($array[self::KEY_PRESENCE_PENALTY]);
        }
        if (isset($array[self::KEY_FREQUENCY_PENALTY])) {
            $config->setFrequencyPenalty($array[self::KEY_FREQUENCY_PENALTY]);
        }
        if (isset($array[self::KEY_LOGPROBS])) {
            $config->setLogprobs($array[self::KEY_LOGPROBS]);
        }
        if (isset($array[self::KEY_TOP_LOGPROBS])) {
            $config->setTopLogprobs($array[self::KEY_TOP_LOGPROBS]);
        }
        if (isset($array[self::KEY_FUNCTION_DECLARATIONS])) {
            $config->setFunctionDeclarations(array_map(static function (array $functionDeclarationData): FunctionDeclaration {
                return FunctionDeclaration::fromArray($functionDeclarationData);
            }, $array[self::KEY_FUNCTION_DECLARATIONS]));
        }
        if (isset($array[self::KEY_WEB_SEARCH])) {
            $config->setWebSearch(WebSearch::fromArray($array[self::KEY_WEB_SEARCH]));
        }
        if (isset($array[self::KEY_OUTPUT_FILE_TYPE])) {
            $config->setOutputFileType(FileTypeEnum::from($array[self::KEY_OUTPUT_FILE_TYPE]));
        }
        if (isset($array[self::KEY_OUTPUT_MIME_TYPE])) {
            $config->setOutputMimeType($array[self::KEY_OUTPUT_MIME_TYPE]);
        }
        if (isset($array[self::KEY_OUTPUT_SCHEMA])) {
            $config->setOutputSchema($array[self::KEY_OUTPUT_SCHEMA]);
        }
        if (isset($array[self::KEY_OUTPUT_MEDIA_ORIENTATION])) {
            $config->setOutputMediaOrientation(MediaOrientationEnum::from($array[self::KEY_OUTPUT_MEDIA_ORIENTATION]));
        }
        if (isset($array[self::KEY_OUTPUT_MEDIA_ASPECT_RATIO])) {
            $config->setOutputMediaAspectRatio($array[self::KEY_OUTPUT_MEDIA_ASPECT_RATIO]);
        }
        if (isset($array[self::KEY_OUTPUT_SPEECH_VOICE])) {
            $config->setOutputSpeechVoice($array[self::KEY_OUTPUT_SPEECH_VOICE]);
        }
        if (isset($array[self::KEY_CUSTOM_OPTIONS])) {
            $config->setCustomOptions($array[self::KEY_CUSTOM_OPTIONS]);
        }
        return $config;
    }
}
PK�e]�:%%WProviders/Models/SpeechGeneration/Contracts/SpeechGenerationOperationModelInterface.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Models\SpeechGeneration\Contracts;

use WordPress\AiClient\Messages\DTO\Message;
use WordPress\AiClient\Operations\DTO\GenerativeAiOperation;
/**
 * Interface for models that support asynchronous speech generation operations.
 *
 * Provides methods for initiating long-running speech generation tasks.
 *
 * @since 0.1.0
 */
interface SpeechGenerationOperationModelInterface
{
    /**
     * Creates a speech generation operation.
     *
     * @since 0.1.0
     *
     * @param list<Message> $prompt Array of messages containing the speech generation prompt.
     * @return GenerativeAiOperation The initiated speech generation operation.
     */
    public function generateSpeechOperation(array $prompt): GenerativeAiOperation;
}
PK�e]*Ue5��NProviders/Models/SpeechGeneration/Contracts/SpeechGenerationModelInterface.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Models\SpeechGeneration\Contracts;

use WordPress\AiClient\Messages\DTO\Message;
use WordPress\AiClient\Results\DTO\GenerativeAiResult;
/**
 * Interface for models that support speech generation.
 *
 * Provides synchronous methods for generating speech from prompts.
 *
 * @since 0.1.0
 */
interface SpeechGenerationModelInterface
{
    /**
     * Generates speech from a prompt.
     *
     * @since 0.1.0
     *
     * @param list<Message> $prompt Array of messages containing the speech generation prompt.
     * @return GenerativeAiResult Result containing generated speech audio.
     */
    public function generateSpeechResult(array $prompt): GenerativeAiResult;
}
PK�e][.�i))%Providers/Models/Enums/OptionEnum.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Models\Enums;

use ReflectionClass;
use WordPress\AiClient\Common\AbstractEnum;
use WordPress\AiClient\Providers\Models\DTO\ModelConfig;
/**
 * Enum for model options.
 *
 * This enum dynamically includes all options from ModelConfig KEY_* constants
 * in addition to the explicitly defined constants below.
 *
 * Explicitly defined option (not in ModelConfig):
 * @method static self inputModalities() Creates an instance for INPUT_MODALITIES option.
 * @method bool isInputModalities() Checks if the option is INPUT_MODALITIES.
 *
 * Dynamically loaded from ModelConfig KEY_* constants:
 * @method static self candidateCount() Creates an instance for CANDIDATE_COUNT option.
 * @method static self customOptions() Creates an instance for CUSTOM_OPTIONS option.
 * @method static self frequencyPenalty() Creates an instance for FREQUENCY_PENALTY option.
 * @method static self functionDeclarations() Creates an instance for FUNCTION_DECLARATIONS option.
 * @method static self logprobs() Creates an instance for LOGPROBS option.
 * @method static self maxTokens() Creates an instance for MAX_TOKENS option.
 * @method static self outputFileType() Creates an instance for OUTPUT_FILE_TYPE option.
 * @method static self outputMediaAspectRatio() Creates an instance for OUTPUT_MEDIA_ASPECT_RATIO option.
 * @method static self outputMediaOrientation() Creates an instance for OUTPUT_MEDIA_ORIENTATION option.
 * @method static self outputMimeType() Creates an instance for OUTPUT_MIME_TYPE option.
 * @method static self outputModalities() Creates an instance for OUTPUT_MODALITIES option.
 * @method static self outputSchema() Creates an instance for OUTPUT_SCHEMA option.
 * @method static self outputSpeechVoice() Creates an instance for OUTPUT_SPEECH_VOICE option.
 * @method static self presencePenalty() Creates an instance for PRESENCE_PENALTY option.
 * @method static self stopSequences() Creates an instance for STOP_SEQUENCES option.
 * @method static self systemInstruction() Creates an instance for SYSTEM_INSTRUCTION option.
 * @method static self temperature() Creates an instance for TEMPERATURE option.
 * @method static self topK() Creates an instance for TOP_K option.
 * @method static self topLogprobs() Creates an instance for TOP_LOGPROBS option.
 * @method static self topP() Creates an instance for TOP_P option.
 * @method static self webSearch() Creates an instance for WEB_SEARCH option.
 * @method bool isCandidateCount() Checks if the option is CANDIDATE_COUNT.
 * @method bool isCustomOptions() Checks if the option is CUSTOM_OPTIONS.
 * @method bool isFrequencyPenalty() Checks if the option is FREQUENCY_PENALTY.
 * @method bool isFunctionDeclarations() Checks if the option is FUNCTION_DECLARATIONS.
 * @method bool isLogprobs() Checks if the option is LOGPROBS.
 * @method bool isMaxTokens() Checks if the option is MAX_TOKENS.
 * @method bool isOutputFileType() Checks if the option is OUTPUT_FILE_TYPE.
 * @method bool isOutputMediaAspectRatio() Checks if the option is OUTPUT_MEDIA_ASPECT_RATIO.
 * @method bool isOutputMediaOrientation() Checks if the option is OUTPUT_MEDIA_ORIENTATION.
 * @method bool isOutputMimeType() Checks if the option is OUTPUT_MIME_TYPE.
 * @method bool isOutputModalities() Checks if the option is OUTPUT_MODALITIES.
 * @method bool isOutputSchema() Checks if the option is OUTPUT_SCHEMA.
 * @method bool isOutputSpeechVoice() Checks if the option is OUTPUT_SPEECH_VOICE.
 * @method bool isPresencePenalty() Checks if the option is PRESENCE_PENALTY.
 * @method bool isStopSequences() Checks if the option is STOP_SEQUENCES.
 * @method bool isSystemInstruction() Checks if the option is SYSTEM_INSTRUCTION.
 * @method bool isTemperature() Checks if the option is TEMPERATURE.
 * @method bool isTopK() Checks if the option is TOP_K.
 * @method bool isTopLogprobs() Checks if the option is TOP_LOGPROBS.
 * @method bool isTopP() Checks if the option is TOP_P.
 * @method bool isWebSearch() Checks if the option is WEB_SEARCH.
 *
 * @since 0.1.0
 */
class OptionEnum extends AbstractEnum
{
    /**
     * Input modalities option.
     *
     * This constant is not in ModelConfig as it's derived from message content,
     * not configured directly.
     */
    public const INPUT_MODALITIES = 'input_modalities';
    /**
     * Determines the class enumerations by reflecting on class constants.
     *
     * Overrides the parent method to dynamically add constants from ModelConfig
     * that are prefixed with KEY_. These are transformed to remove the KEY_ prefix
     * and converted to snake_case values.
     *
     * @since 0.1.0
     *
     * @param class-string $className The fully qualified class name.
     * @return array<string, string> The enum constants.
     */
    protected static function determineClassEnumerations(string $className): array
    {
        // Start with the constants defined in this class using parent method
        $constants = parent::determineClassEnumerations($className);
        // Use reflection to get all constants from ModelConfig
        $modelConfigReflection = new ReflectionClass(ModelConfig::class);
        $modelConfigConstants = $modelConfigReflection->getConstants();
        // Add ModelConfig constants that start with KEY_
        foreach ($modelConfigConstants as $constantName => $constantValue) {
            if (str_starts_with($constantName, 'KEY_')) {
                // Remove KEY_ prefix to get the enum constant name
                $enumConstantName = substr($constantName, 4);
                // The value is the snake_case version stored in ModelConfig
                // ModelConfig already stores these as snake_case strings
                if (is_string($constantValue)) {
                    $constants[$enumConstantName] = $constantValue;
                }
            }
        }
        return $constants;
    }
}
PK�e]���2

)Providers/Models/Enums/CapabilityEnum.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Models\Enums;

use WordPress\AiClient\Common\AbstractEnum;
/**
 * Enum for model capabilities.
 *
 * @since 0.1.0
 *
 * @method static self textGeneration() Creates an instance for TEXT_GENERATION capability.
 * @method static self imageGeneration() Creates an instance for IMAGE_GENERATION capability.
 * @method static self textToSpeechConversion() Creates an instance for TEXT_TO_SPEECH_CONVERSION capability.
 * @method static self speechGeneration() Creates an instance for SPEECH_GENERATION capability.
 * @method static self musicGeneration() Creates an instance for MUSIC_GENERATION capability.
 * @method static self videoGeneration() Creates an instance for VIDEO_GENERATION capability.
 * @method static self embeddingGeneration() Creates an instance for EMBEDDING_GENERATION capability.
 * @method static self chatHistory() Creates an instance for CHAT_HISTORY capability.
 * @method bool isTextGeneration() Checks if the capability is TEXT_GENERATION.
 * @method bool isImageGeneration() Checks if the capability is IMAGE_GENERATION.
 * @method bool isTextToSpeechConversion() Checks if the capability is TEXT_TO_SPEECH_CONVERSION.
 * @method bool isSpeechGeneration() Checks if the capability is SPEECH_GENERATION.
 * @method bool isMusicGeneration() Checks if the capability is MUSIC_GENERATION.
 * @method bool isVideoGeneration() Checks if the capability is VIDEO_GENERATION.
 * @method bool isEmbeddingGeneration() Checks if the capability is EMBEDDING_GENERATION.
 * @method bool isChatHistory() Checks if the capability is CHAT_HISTORY.
 */
class CapabilityEnum extends AbstractEnum
{
    /**
     * Text generation capability.
     */
    public const TEXT_GENERATION = 'text_generation';
    /**
     * Image generation capability.
     */
    public const IMAGE_GENERATION = 'image_generation';
    /**
     * Text to speech conversion capability.
     */
    public const TEXT_TO_SPEECH_CONVERSION = 'text_to_speech_conversion';
    /**
     * Speech generation capability.
     */
    public const SPEECH_GENERATION = 'speech_generation';
    /**
     * Music generation capability.
     */
    public const MUSIC_GENERATION = 'music_generation';
    /**
     * Video generation capability.
     */
    public const VIDEO_GENERATION = 'video_generation';
    /**
     * Embedding generation capability.
     */
    public const EMBEDDING_GENERATION = 'embedding_generation';
    /**
     * Chat history support capability.
     */
    public const CHAT_HISTORY = 'chat_history';
}
PK�e]԰W���LProviders/Models/ImageGeneration/Contracts/ImageGenerationModelInterface.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Models\ImageGeneration\Contracts;

use WordPress\AiClient\Messages\DTO\Message;
use WordPress\AiClient\Results\DTO\GenerativeAiResult;
/**
 * Interface for models that support image generation.
 *
 * Provides synchronous methods for generating images from text prompts.
 *
 * @since 0.1.0
 */
interface ImageGenerationModelInterface
{
    /**
     * Generates images from a prompt.
     *
     * @since 0.1.0
     *
     * @param list<Message> $prompt Array of messages containing the image generation prompt.
     * @return GenerativeAiResult Result containing generated images.
     */
    public function generateImageResult(array $prompt): GenerativeAiResult;
}
PK�e]M)z�UProviders/Models/ImageGeneration/Contracts/ImageGenerationOperationModelInterface.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Models\ImageGeneration\Contracts;

use WordPress\AiClient\Messages\DTO\Message;
use WordPress\AiClient\Operations\DTO\GenerativeAiOperation;
/**
 * Interface for models that support asynchronous image generation operations.
 *
 * Provides methods for initiating long-running image generation tasks.
 *
 * @since 0.1.0
 */
interface ImageGenerationOperationModelInterface
{
    /**
     * Creates an image generation operation.
     *
     * @since 0.1.0
     *
     * @param list<Message> $prompt Array of messages containing the image generation prompt.
     * @return GenerativeAiOperation The initiated image generation operation.
     */
    public function generateImageOperation(array $prompt): GenerativeAiOperation;
}
PK�e]{�9���-Providers/Models/Contracts/ModelInterface.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Models\Contracts;

use WordPress\AiClient\Providers\DTO\ProviderMetadata;
use WordPress\AiClient\Providers\Models\DTO\ModelConfig;
use WordPress\AiClient\Providers\Models\DTO\ModelMetadata;
/**
 * Interface for AI models.
 *
 * Models represent specific AI models from providers and define
 * their capabilities, configuration, and execution methods.
 *
 * @since 0.1.0
 */
interface ModelInterface
{
    /**
     * Gets model metadata.
     *
     * @since 0.1.0
     *
     * @return ModelMetadata Model metadata.
     */
    public function metadata(): ModelMetadata;
    /**
     * Returns the metadata for the model's provider.
     *
     * @since 0.1.0
     *
     * @return ProviderMetadata The provider metadata.
     */
    public function providerMetadata(): ProviderMetadata;
    /**
     * Sets model configuration.
     *
     * @since 0.1.0
     *
     * @param ModelConfig $config Model configuration.
     * @return void
     */
    public function setConfig(ModelConfig $config): void;
    /**
     * Gets model configuration.
     *
     * @since 0.1.0
     *
     * @return ModelConfig Current model configuration.
     */
    public function getConfig(): ModelConfig;
}
PK�e]���SProviders/Models/TextGeneration/Contracts/TextGenerationOperationModelInterface.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Models\TextGeneration\Contracts;

use WordPress\AiClient\Messages\DTO\Message;
use WordPress\AiClient\Operations\DTO\GenerativeAiOperation;
/**
 * Interface for models that support asynchronous text generation operations.
 *
 * Provides methods for initiating long-running text generation tasks.
 *
 * @since 0.1.0
 */
interface TextGenerationOperationModelInterface
{
    /**
     * Creates a text generation operation.
     *
     * @since 0.1.0
     *
     * @param list<Message> $prompt Array of messages containing the text generation prompt.
     * @return GenerativeAiOperation The initiated text generation operation.
     */
    public function generateTextOperation(array $prompt): GenerativeAiOperation;
}
PK�e]�`���JProviders/Models/TextGeneration/Contracts/TextGenerationModelInterface.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Models\TextGeneration\Contracts;

use WordPress\AiClient\Messages\DTO\Message;
use WordPress\AiClient\Results\DTO\GenerativeAiResult;
/**
 * Interface for models that support text generation.
 *
 * Provides synchronous and streaming methods for generating text from prompts.
 *
 * @since 0.1.0
 */
interface TextGenerationModelInterface
{
    /**
     * Generates text from a prompt.
     *
     * @since 0.1.0
     *
     * @param list<Message> $prompt Array of messages containing the text generation prompt.
     * @return GenerativeAiResult Result containing generated text.
     */
    public function generateTextResult(array $prompt): GenerativeAiResult;
}
PK�e]�=�A��KProviders/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\ApiBasedImplementation;

use WordPress\AiClient\AiClient;
use WordPress\AiClient\Common\Contracts\CachesDataInterface;
use WordPress\AiClient\Common\Exception\InvalidArgumentException;
use WordPress\AiClient\Common\Traits\WithDataCachingTrait;
use WordPress\AiClient\Providers\Contracts\ModelMetadataDirectoryInterface;
use WordPress\AiClient\Providers\Http\Contracts\WithHttpTransporterInterface;
use WordPress\AiClient\Providers\Http\Contracts\WithRequestAuthenticationInterface;
use WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait;
use WordPress\AiClient\Providers\Http\Traits\WithRequestAuthenticationTrait;
use WordPress\AiClient\Providers\Models\DTO\ModelMetadata;
/**
 * Base class for an API-based model metadata directory for a provider.
 *
 * @since 0.1.0
 */
abstract class AbstractApiBasedModelMetadataDirectory implements ModelMetadataDirectoryInterface, WithHttpTransporterInterface, WithRequestAuthenticationInterface, CachesDataInterface
{
    use WithHttpTransporterTrait;
    use WithRequestAuthenticationTrait;
    use WithDataCachingTrait;
    /**
     * The cache key suffix for the models list.
     *
     * @since 0.4.0
     *
     * @var string
     */
    private const MODELS_CACHE_KEY = 'models';
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    final public function listModelMetadata(): array
    {
        $modelsMetadata = $this->getModelMetadataMap();
        return array_values($modelsMetadata);
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    final public function hasModelMetadata(string $modelId): bool
    {
        $modelsMetadata = $this->getModelMetadataMap();
        return isset($modelsMetadata[$modelId]);
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    final public function getModelMetadata(string $modelId): ModelMetadata
    {
        $modelsMetadata = $this->getModelMetadataMap();
        if (!isset($modelsMetadata[$modelId])) {
            throw new InvalidArgumentException(sprintf('No model with ID %s was found in the provider', $modelId));
        }
        return $modelsMetadata[$modelId];
    }
    /**
     * Returns the map of model ID to model metadata for all models from the provider.
     *
     * @since 0.1.0
     *
     * @return array<string, ModelMetadata> Map of model ID to model metadata.
     */
    private function getModelMetadataMap(): array
    {
        /** @var array<string, ModelMetadata> */
        return $this->cached(self::MODELS_CACHE_KEY, fn() => $this->sendListModelsRequest(), 86400);
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.4.0
     */
    protected function getCachedKeys(): array
    {
        return [self::MODELS_CACHE_KEY];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.4.0
     */
    protected function getBaseCacheKey(): string
    {
        return 'ai_client_' . AiClient::VERSION . '_' . md5(static::class);
    }
    /**
     * Sends the API request to list models from the provider and returns the map of model ID to model metadata.
     *
     * @since 0.1.0
     *
     * @return array<string, ModelMetadata> Map of model ID to model metadata.
     */
    abstract protected function sendListModelsRequest(): array;
}
PK�e]l���8Providers/ApiBasedImplementation/AbstractApiProvider.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\ApiBasedImplementation;

use WordPress\AiClient\Providers\AbstractProvider;
/**
 * Base class for API-based providers.
 *
 * This abstract class provides URL construction utilities for providers that
 * communicate with REST APIs. It standardizes the pattern of combining a base
 * URL with endpoint paths.
 *
 * @since 0.2.0
 */
abstract class AbstractApiProvider extends AbstractProvider
{
    /**
     * Gets the base URL for the provider's API.
     *
     * The base URL should include the protocol and domain, and may include
     * the API version path (e.g., "https://api.example.com/v1").
     *
     * @since 0.2.0
     *
     * @return string The base URL for the provider's API.
     */
    abstract protected static function baseUrl(): string;
    /**
     * Constructs a full URL by combining the base URL with an optional path.
     *
     * This method ensures proper URL construction by:
     * - Using the provider's base URL
     * - Trimming leading slashes from the path to prevent double-slashes
     * - Joining the base URL and path with a single forward slash
     *
     * @since 0.2.0
     *
     * @param string $path Optional path to append to the base URL. Default empty string.
     * @return string The complete URL.
     */
    public static function url(string $path = ''): string
    {
        if ($path === '') {
            return static::baseUrl();
        }
        return static::baseUrl() . '/' . ltrim($path, '/');
    }
}
PK�e]��t��:Providers/ApiBasedImplementation/AbstractApiBasedModel.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\ApiBasedImplementation;

use WordPress\AiClient\Providers\ApiBasedImplementation\Contracts\ApiBasedModelInterface;
use WordPress\AiClient\Providers\DTO\ProviderMetadata;
use WordPress\AiClient\Providers\Http\Contracts\WithHttpTransporterInterface;
use WordPress\AiClient\Providers\Http\Contracts\WithRequestAuthenticationInterface;
use WordPress\AiClient\Providers\Http\DTO\RequestOptions;
use WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait;
use WordPress\AiClient\Providers\Http\Traits\WithRequestAuthenticationTrait;
use WordPress\AiClient\Providers\Models\DTO\ModelConfig;
use WordPress\AiClient\Providers\Models\DTO\ModelMetadata;
/**
 * Base class for an API-based model for a provider.
 *
 * While this class contains no abstract methods, it is still abstract to ensure that each model class can actually
 * perform generative AI tasks by implementing the corresponding interfaces.
 *
 * @since 0.1.0
 */
abstract class AbstractApiBasedModel implements ApiBasedModelInterface, WithHttpTransporterInterface, WithRequestAuthenticationInterface
{
    use WithHttpTransporterTrait;
    use WithRequestAuthenticationTrait;
    /**
     * @var ModelMetadata The metadata for the model.
     */
    private ModelMetadata $metadata;
    /**
     * @var ProviderMetadata The metadata for the model's provider.
     */
    private ProviderMetadata $providerMetadata;
    /**
     * @var ModelConfig The configuration for the model.
     */
    private ModelConfig $config;
    /**
     * @var RequestOptions|null The request options for HTTP transport.
     */
    private ?RequestOptions $requestOptions = null;
    /**
     * Constructor.
     *
     * @since 0.1.0
     *
     * @param ModelMetadata $metadata The metadata for the model.
     * @param ProviderMetadata $providerMetadata The metadata for the model's provider.
     */
    public function __construct(ModelMetadata $metadata, ProviderMetadata $providerMetadata)
    {
        $this->metadata = $metadata;
        $this->providerMetadata = $providerMetadata;
        $this->config = ModelConfig::fromArray([]);
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    final public function metadata(): ModelMetadata
    {
        return $this->metadata;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    final public function providerMetadata(): ProviderMetadata
    {
        return $this->providerMetadata;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    final public function setConfig(ModelConfig $config): void
    {
        $this->config = $config;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    final public function getConfig(): ModelConfig
    {
        return $this->config;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.3.0
     */
    final public function setRequestOptions(RequestOptions $requestOptions): void
    {
        $this->requestOptions = $requestOptions;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.3.0
     */
    final public function getRequestOptions(): ?RequestOptions
    {
        return $this->requestOptions;
    }
}
PK�e]�qo�KProviders/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\ApiBasedImplementation;

use Exception;
use WordPress\AiClient\Providers\Contracts\ModelMetadataDirectoryInterface;
use WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface;
/**
 * Class to check availability for an API-based provider via a test request to the endpoint to list models.
 *
 * This class should be used for cloud-based providers that offer a model listing endpoint which requires
 * authentication. A request to this endpoint is used to determine if the provider is properly configured
 * with valid credentials.
 *
 * @since 0.1.0
 */
class ListModelsApiBasedProviderAvailability implements ProviderAvailabilityInterface
{
    /**
     * @var ModelMetadataDirectoryInterface The model metadata directory to use for checking availability.
     */
    private ModelMetadataDirectoryInterface $modelMetadataDirectory;
    /**
     * Constructor.
     *
     * @since 0.1.0
     *
     * @param ModelMetadataDirectoryInterface $modelMetadataDirectory The model metadata directory to use for checking
     *                                                                availability.
     */
    public function __construct(ModelMetadataDirectoryInterface $modelMetadataDirectory)
    {
        $this->modelMetadataDirectory = $modelMetadataDirectory;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public function isConfigured(): bool
    {
        try {
            // Attempt to list models to check if the provider is available.
            $this->modelMetadataDirectory->listModelMetadata();
            return \true;
        } catch (Exception $e) {
            // If an exception occurs, the provider is not available.
            return \false;
        }
    }
}
PK�e]��`"@	@	MProviders/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\ApiBasedImplementation;

use Exception;
use WordPress\AiClient\Messages\DTO\Message;
use WordPress\AiClient\Messages\DTO\MessagePart;
use WordPress\AiClient\Messages\Enums\MessageRoleEnum;
use WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface;
use WordPress\AiClient\Providers\Models\Contracts\ModelInterface;
use WordPress\AiClient\Providers\Models\DTO\ModelConfig;
use WordPress\AiClient\Providers\Models\TextGeneration\Contracts\TextGenerationModelInterface;
/**
 * Class to check availability for an API-based provider via a test request to the endpoint to generate text.
 *
 * This class should be used for cloud-based providers that do not offer a model listing endpoint, but do offer a
 * text generation endpoint which requires authentication. A minimal request to this endpoint is used to determine
 * if the provider is properly configured with valid credentials.
 *
 * @since 0.1.0
 */
class GenerateTextApiBasedProviderAvailability implements ProviderAvailabilityInterface
{
    /**
     * @var ModelInterface&TextGenerationModelInterface The model to use for checking availability.
     */
    private ModelInterface $model;
    /**
     * Constructor.
     *
     * @since 0.1.0
     *
     * @param ModelInterface $model The model to use for checking availability.
     */
    public function __construct(ModelInterface $model)
    {
        if (!$model instanceof TextGenerationModelInterface) {
            throw new Exception('The model class to check provider availability must implement TextGenerationModelInterface.');
        }
        $this->model = $model;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public function isConfigured(): bool
    {
        // Set config to use as few resources as possible for the test.
        $modelConfig = ModelConfig::fromArray([ModelConfig::KEY_MAX_TOKENS => 1]);
        $this->model->setConfig($modelConfig);
        try {
            // Attempt to generate text to check if the provider is available.
            $this->model->generateTextResult([new Message(MessageRoleEnum::user(), [new MessagePart('a')])]);
            return \true;
        } catch (Exception $e) {
            // If an exception occurs, the provider is not available.
            return \false;
        }
    }
}
PK�e]��EProviders/ApiBasedImplementation/Contracts/ApiBasedModelInterface.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\ApiBasedImplementation\Contracts;

use WordPress\AiClient\Providers\Http\DTO\RequestOptions;
use WordPress\AiClient\Providers\Models\Contracts\ModelInterface;
/**
 * Interface for API-based AI models that support HTTP transport configuration.
 *
 * This interface extends ModelInterface to add request options support
 * for models that communicate with external APIs via HTTP.
 *
 * @since 0.3.0
 */
interface ApiBasedModelInterface extends ModelInterface
{
    /**
     * Sets the request options for HTTP transport.
     *
     * @since 0.3.0
     *
     * @param RequestOptions $requestOptions The request options to use.
     * @return void
     */
    public function setRequestOptions(RequestOptions $requestOptions): void;
    /**
     * Gets the request options for HTTP transport.
     *
     * @since 0.3.0
     *
     * @return RequestOptions|null The request options, or null if not set.
     */
    public function getRequestOptions(): ?RequestOptions;
}
PK�e]N����"Providers/DTO/ProviderMetadata.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\DTO;

use WordPress\AiClient\Common\AbstractDataTransferObject;
use WordPress\AiClient\Common\Exception\InvalidArgumentException;
use WordPress\AiClient\Providers\Enums\ProviderTypeEnum;
use WordPress\AiClient\Providers\Http\Enums\RequestAuthenticationMethod;
/**
 * Represents metadata about an AI provider.
 *
 * This class contains information about an AI provider, including its
 * unique identifier, display name, and type (cloud, server, or client).
 *
 * @since 0.1.0
 * @since 1.2.0 Added optional description property.
 * @since 1.3.0 Added optional logoPath property.
 *
 * @phpstan-type ProviderMetadataArrayShape array{
 *     id: string,
 *     name: string,
 *     description?: ?string,
 *     type: string,
 *     credentialsUrl?: ?string,
 *     authenticationMethod?: ?string,
 *     logoPath?: ?string
 * }
 *
 * @extends AbstractDataTransferObject<ProviderMetadataArrayShape>
 */
class ProviderMetadata extends AbstractDataTransferObject
{
    public const KEY_ID = 'id';
    public const KEY_NAME = 'name';
    public const KEY_DESCRIPTION = 'description';
    public const KEY_TYPE = 'type';
    public const KEY_CREDENTIALS_URL = 'credentialsUrl';
    public const KEY_AUTHENTICATION_METHOD = 'authenticationMethod';
    public const KEY_LOGO_PATH = 'logoPath';
    /**
     * @var string The provider's unique identifier.
     */
    protected string $id;
    /**
     * @var string The provider's display name.
     */
    protected string $name;
    /**
     * @var string|null The provider's description.
     */
    protected ?string $description;
    /**
     * @var ProviderTypeEnum The provider type.
     */
    protected ProviderTypeEnum $type;
    /**
     * @var string|null The URL where users can get credentials.
     */
    protected ?string $credentialsUrl;
    /**
     * @var RequestAuthenticationMethod|null The authentication method.
     */
    protected ?RequestAuthenticationMethod $authenticationMethod;
    /**
     * @var string|null The full path to the provider's logo image file.
     */
    protected ?string $logoPath;
    /**
     * Constructor.
     *
     * @since 0.1.0
     * @since 1.2.0 Added optional $description parameter.
     * @since 1.3.0 Added optional $logoPath parameter.
     *
     * @param string $id The provider's unique identifier.
     * @param string $name The provider's display name.
     * @param ProviderTypeEnum $type The provider type.
     * @param string|null $credentialsUrl The URL where users can get credentials.
     * @param RequestAuthenticationMethod|null $authenticationMethod The authentication method.
     * @param string|null $description The provider's description.
     * @param string|null $logoPath The full path to the provider's logo image file.
     * @throws InvalidArgumentException If the provider ID contains invalid characters.
     */
    public function __construct(string $id, string $name, ProviderTypeEnum $type, ?string $credentialsUrl = null, ?RequestAuthenticationMethod $authenticationMethod = null, ?string $description = null, ?string $logoPath = null)
    {
        if (!preg_match('/^[a-z0-9\-_]+$/', $id)) {
            throw new InvalidArgumentException(sprintf(
                // phpcs:ignore Generic.Files.LineLength.TooLong
                'Invalid provider ID "%s". Only lowercase alphanumeric characters, hyphens, and underscores are allowed.',
                $id
            ));
        }
        $this->id = $id;
        $this->name = $name;
        $this->description = $description;
        $this->type = $type;
        $this->credentialsUrl = $credentialsUrl;
        $this->authenticationMethod = $authenticationMethod;
        $this->logoPath = $logoPath;
    }
    /**
     * Gets the provider's unique identifier.
     *
     * @since 0.1.0
     *
     * @return string The provider ID.
     */
    public function getId(): string
    {
        return $this->id;
    }
    /**
     * Gets the provider's display name.
     *
     * @since 0.1.0
     *
     * @return string The provider name.
     */
    public function getName(): string
    {
        return $this->name;
    }
    /**
     * Gets the provider's description.
     *
     * @since 1.2.0
     *
     * @return string|null The provider description.
     */
    public function getDescription(): ?string
    {
        return $this->description;
    }
    /**
     * Gets the provider type.
     *
     * @since 0.1.0
     *
     * @return ProviderTypeEnum The provider type.
     */
    public function getType(): ProviderTypeEnum
    {
        return $this->type;
    }
    /**
     * Gets the credentials URL.
     *
     * @since 0.1.0
     *
     * @return string|null The credentials URL.
     */
    public function getCredentialsUrl(): ?string
    {
        return $this->credentialsUrl;
    }
    /**
     * Gets the authentication method.
     *
     * @since 0.4.0
     *
     * @return RequestAuthenticationMethod|null The authentication method.
     */
    public function getAuthenticationMethod(): ?RequestAuthenticationMethod
    {
        return $this->authenticationMethod;
    }
    /**
     * Gets the full path to the provider's logo image file.
     *
     * @since 1.3.0
     *
     * @return string|null The full path to the logo image file.
     */
    public function getLogoPath(): ?string
    {
        return $this->logoPath;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     * @since 1.2.0 Added description to schema.
     * @since 1.3.0 Added logoPath to schema.
     */
    public static function getJsonSchema(): array
    {
        return ['type' => 'object', 'properties' => [self::KEY_ID => ['type' => 'string', 'description' => 'The provider\'s unique identifier.'], self::KEY_NAME => ['type' => 'string', 'description' => 'The provider\'s display name.'], self::KEY_DESCRIPTION => ['type' => 'string', 'description' => 'The provider\'s description.'], self::KEY_TYPE => ['type' => 'string', 'enum' => ProviderTypeEnum::getValues(), 'description' => 'The provider type (cloud, server, or client).'], self::KEY_CREDENTIALS_URL => ['type' => 'string', 'description' => 'The URL where users can get credentials.'], self::KEY_AUTHENTICATION_METHOD => ['type' => ['string', 'null'], 'enum' => array_merge(RequestAuthenticationMethod::getValues(), [null]), 'description' => 'The authentication method.'], self::KEY_LOGO_PATH => ['type' => 'string', 'description' => 'The full path to the provider\'s logo image file.']], 'required' => [self::KEY_ID, self::KEY_NAME, self::KEY_TYPE]];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     * @since 1.2.0 Added description to output.
     * @since 1.3.0 Added logoPath to output.
     *
     * @return ProviderMetadataArrayShape
     */
    public function toArray(): array
    {
        return [self::KEY_ID => $this->id, self::KEY_NAME => $this->name, self::KEY_DESCRIPTION => $this->description, self::KEY_TYPE => $this->type->value, self::KEY_CREDENTIALS_URL => $this->credentialsUrl, self::KEY_AUTHENTICATION_METHOD => $this->authenticationMethod ? $this->authenticationMethod->value : null, self::KEY_LOGO_PATH => $this->logoPath];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     * @since 1.2.0 Added description support.
     * @since 1.3.0 Added logoPath support.
     */
    public static function fromArray(array $array): self
    {
        static::validateFromArrayData($array, [self::KEY_ID, self::KEY_NAME, self::KEY_TYPE]);
        return new self($array[self::KEY_ID], $array[self::KEY_NAME], ProviderTypeEnum::from($array[self::KEY_TYPE]), $array[self::KEY_CREDENTIALS_URL] ?? null, isset($array[self::KEY_AUTHENTICATION_METHOD]) ? RequestAuthenticationMethod::from($array[self::KEY_AUTHENTICATION_METHOD]) : null, $array[self::KEY_DESCRIPTION] ?? null, $array[self::KEY_LOGO_PATH] ?? null);
    }
}
PK�e]2���dd(Providers/DTO/ProviderModelsMetadata.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\DTO;

use WordPress\AiClient\Common\AbstractDataTransferObject;
use WordPress\AiClient\Common\Exception\InvalidArgumentException;
use WordPress\AiClient\Providers\Models\DTO\ModelMetadata;
/**
 * Represents metadata about a provider and its available models.
 *
 * This class combines provider information with the models that
 * the provider offers, facilitating model discovery and selection.
 *
 * @since 0.1.0
 *
 * @phpstan-import-type ProviderMetadataArrayShape from ProviderMetadata
 * @phpstan-import-type ModelMetadataArrayShape from ModelMetadata
 *
 * @phpstan-type ProviderModelsMetadataArrayShape array{
 *     provider: ProviderMetadataArrayShape,
 *     models: list<ModelMetadataArrayShape>
 * }
 *
 * @extends AbstractDataTransferObject<ProviderModelsMetadataArrayShape>
 */
class ProviderModelsMetadata extends AbstractDataTransferObject
{
    public const KEY_PROVIDER = 'provider';
    public const KEY_MODELS = 'models';
    /**
     * @var ProviderMetadata The provider metadata.
     */
    protected \WordPress\AiClient\Providers\DTO\ProviderMetadata $provider;
    /**
     * @var list<ModelMetadata> The available models.
     */
    protected array $models;
    /**
     * Constructor.
     *
     * @since 0.1.0
     *
     * @param ProviderMetadata $provider The provider metadata.
     * @param list<ModelMetadata> $models The available models.
     *
     * @throws InvalidArgumentException If models is not a list.
     */
    public function __construct(\WordPress\AiClient\Providers\DTO\ProviderMetadata $provider, array $models)
    {
        if (!array_is_list($models)) {
            throw new InvalidArgumentException('Models must be a list array.');
        }
        $this->provider = $provider;
        $this->models = $models;
    }
    /**
     * Creates a deep clone of this metadata.
     *
     * Clones the provider metadata and all model metadata objects
     * to ensure the cloned instance is independent of the original.
     *
     * @since 0.4.2
     */
    public function __clone()
    {
        // Clone provider metadata
        $this->provider = clone $this->provider;
        // Deep clone models array (ModelMetadata has __clone)
        $clonedModels = [];
        foreach ($this->models as $model) {
            $clonedModels[] = clone $model;
        }
        $this->models = $clonedModels;
    }
    /**
     * Gets the provider metadata.
     *
     * @since 0.1.0
     *
     * @return ProviderMetadata The provider metadata.
     */
    public function getProvider(): \WordPress\AiClient\Providers\DTO\ProviderMetadata
    {
        return $this->provider;
    }
    /**
     * Gets the available models.
     *
     * @since 0.1.0
     *
     * @return list<ModelMetadata> The available models.
     */
    public function getModels(): array
    {
        return $this->models;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function getJsonSchema(): array
    {
        return ['type' => 'object', 'properties' => [self::KEY_PROVIDER => \WordPress\AiClient\Providers\DTO\ProviderMetadata::getJsonSchema(), self::KEY_MODELS => ['type' => 'array', 'items' => ModelMetadata::getJsonSchema(), 'description' => 'The available models for this provider.']], 'required' => [self::KEY_PROVIDER, self::KEY_MODELS]];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     *
     * @return ProviderModelsMetadataArrayShape
     */
    public function toArray(): array
    {
        return [self::KEY_PROVIDER => $this->provider->toArray(), self::KEY_MODELS => array_map(static fn(ModelMetadata $model): array => $model->toArray(), $this->models)];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function fromArray(array $array): self
    {
        static::validateFromArrayData($array, [self::KEY_PROVIDER, self::KEY_MODELS]);
        return new self(\WordPress\AiClient\Providers\DTO\ProviderMetadata::fromArray($array[self::KEY_PROVIDER]), array_map(static fn(array $modelData): ModelMetadata => ModelMetadata::fromArray($modelData), $array[self::KEY_MODELS]));
    }
}
PK�e]�Goo<Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Http\Abstracts;

use WordPress\AiClientDependencies\Http\Discovery\Psr18ClientDiscovery;
use WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy;
use WordPress\AiClientDependencies\Nyholm\Psr7\Factory\Psr17Factory;
use WordPress\AiClientDependencies\Psr\Http\Client\ClientInterface;
/**
 * Abstract discovery strategy for HTTP client implementations.
 *
 * Provides a base for registering custom HTTP client implementations
 * with HTTPlug's discovery mechanism. Subclasses must implement
 * the createClient() method to provide their specific PSR-18
 * HTTP client instance using the provided Psr17Factory.
 *
 * @since 1.1.0
 */
abstract class AbstractClientDiscoveryStrategy implements DiscoveryStrategy
{
    /**
     * Initializes and registers the discovery strategy.
     *
     * @since 1.1.0
     *
     * @return void
     */
    public static function init(): void
    {
        if (!class_exists('WordPress\AiClientDependencies\Http\Discovery\Psr18ClientDiscovery')) {
            return;
        }
        Psr18ClientDiscovery::prependStrategy(static::class);
    }
    /**
     * {@inheritDoc}
     *
     * @since 1.1.0
     *
     * @param string $type The type of discovery.
     * @return array<array<string, mixed>> The discovery candidates.
     */
    public static function getCandidates($type)
    {
        if (ClientInterface::class === $type) {
            return [['class' => static function () {
                $psr17Factory = new Psr17Factory();
                return static::createClient($psr17Factory);
            }]];
        }
        $psr17Factories = ['WordPress\AiClientDependencies\Psr\Http\Message\RequestFactoryInterface', 'WordPress\AiClientDependencies\Psr\Http\Message\ResponseFactoryInterface', 'WordPress\AiClientDependencies\Psr\Http\Message\ServerRequestFactoryInterface', 'WordPress\AiClientDependencies\Psr\Http\Message\StreamFactoryInterface', 'WordPress\AiClientDependencies\Psr\Http\Message\UploadedFileFactoryInterface', 'WordPress\AiClientDependencies\Psr\Http\Message\UriFactoryInterface'];
        if (in_array($type, $psr17Factories, \true)) {
            return [['class' => Psr17Factory::class]];
        }
        return [];
    }
    /**
     * Creates an instance of the HTTP client.
     *
     * Subclasses must implement this method to return their specific
     * PSR-18 HTTP client instance. The provided Psr17Factory implements
     * all PSR-17 interfaces (RequestFactory, ResponseFactory, StreamFactory,
     * etc.) and can be used to satisfy client constructor dependencies.
     *
     * @since 1.1.0
     *
     * @param Psr17Factory $psr17Factory The PSR-17 factory for creating HTTP messages.
     * @return ClientInterface The PSR-18 HTTP client.
     */
    abstract protected static function createClient(Psr17Factory $psr17Factory): ClientInterface;
}
PK�e]%�)�FF2Providers/Http/Traits/WithHttpTransporterTrait.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Http\Traits;

use WordPress\AiClient\Common\Exception\RuntimeException;
use WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface;
/**
 * Trait for a class that implements WithHttpTransporterInterface.
 *
 * @since 0.1.0
 */
trait WithHttpTransporterTrait
{
    /**
     * @var HttpTransporterInterface|null The HTTP transporter instance.
     */
    private ?HttpTransporterInterface $httpTransporter = null;
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public function setHttpTransporter(HttpTransporterInterface $httpTransporter): void
    {
        $this->httpTransporter = $httpTransporter;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public function getHttpTransporter(): HttpTransporterInterface
    {
        if ($this->httpTransporter === null) {
            throw new RuntimeException('HttpTransporterInterface instance not set. Make sure you use the AiClient class for all requests.');
        }
        return $this->httpTransporter;
    }
}
PK�e]��8��8Providers/Http/Traits/WithRequestAuthenticationTrait.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Http\Traits;

use WordPress\AiClient\Common\Exception\RuntimeException;
use WordPress\AiClient\Providers\Http\Contracts\RequestAuthenticationInterface;
/**
 * Trait for a class that implements WithRequestAuthenticationInterface.
 *
 * @since 0.1.0
 */
trait WithRequestAuthenticationTrait
{
    /**
     * @var RequestAuthenticationInterface|null The request authentication instance.
     */
    private ?RequestAuthenticationInterface $requestAuthentication = null;
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public function setRequestAuthentication(RequestAuthenticationInterface $requestAuthentication): void
    {
        $this->requestAuthentication = $requestAuthentication;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public function getRequestAuthentication(): RequestAuthenticationInterface
    {
        if ($this->requestAuthentication === null) {
            throw new RuntimeException('RequestAuthenticationInterface instance not set. ' . 'Make sure you use the AiClient class for all requests.');
        }
        return $this->requestAuthentication;
    }
}
PK�e]բbG)Providers/Http/HttpTransporterFactory.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Http;

use WordPress\AiClientDependencies\Http\Discovery\Psr17FactoryDiscovery;
use WordPress\AiClientDependencies\Http\Discovery\Psr18ClientDiscovery;
use WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface;
/**
 * Factory for creating HTTP transporters.
 *
 * Uses HTTPlug's Discovery component to automatically find
 * available HTTP clients and factories.
 *
 * @since 0.1.0
 */
class HttpTransporterFactory
{
    /**
     * Creates an HTTP transporter.
     *
     * Uses HTTPlug Discovery to automatically find PSR-18 client
     * and PSR-17 factories if not provided.
     *
     * @since 0.1.0
     *
     * @return HttpTransporterInterface The HTTP transporter.
     */
    public static function createTransporter(): HttpTransporterInterface
    {
        return new \WordPress\AiClient\Providers\Http\HttpTransporter(Psr18ClientDiscovery::find(), Psr17FactoryDiscovery::findRequestFactory(), Psr17FactoryDiscovery::findStreamFactory());
    }
}
PK�e]B,��,Providers/Http/Exception/ServerException.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Http\Exception;

use WordPress\AiClient\Common\Exception\RuntimeException;
use WordPress\AiClient\Providers\Http\DTO\Response;
use WordPress\AiClient\Providers\Http\Util\ErrorMessageExtractor;
/**
 * Exception thrown for 5xx HTTP server errors.
 *
 * This represents errors where the server failed to fulfill
 * a valid request due to internal server errors.
 *
 * @since 0.2.0
 */
class ServerException extends RuntimeException
{
    /**
     * Creates a ServerException from a server error response.
     *
     * This method extracts error details from common API response formats
     * and creates an exception with a descriptive message and status code.
     *
     * @since 0.2.0
     *
     * @param Response $response The HTTP response that failed.
     * @return self
     */
    public static function fromServerErrorResponse(Response $response): self
    {
        $statusCode = $response->getStatusCode();
        $statusTexts = [500 => 'Internal Server Error', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', 507 => 'Insufficient Storage', 529 => 'Overloaded'];
        if (isset($statusTexts[$statusCode])) {
            $errorMessage = sprintf('%s (%d)', $statusTexts[$statusCode], $statusCode);
        } else {
            $errorMessage = sprintf('Server error (%d): Request was rejected due to server-side issue', $statusCode);
        }
        // Extract error message from response data using centralized utility
        $extractedError = ErrorMessageExtractor::extractFromResponseData($response->getData());
        if ($extractedError !== null) {
            $errorMessage .= ' - ' . $extractedError;
        }
        return new self($errorMessage, $response->getStatusCode());
    }
}
PK�e]|գ�55.Providers/Http/Exception/RedirectException.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Http\Exception;

use WordPress\AiClient\Common\Exception\RuntimeException;
use WordPress\AiClient\Providers\Http\DTO\Response;
/**
 * Exception thrown for 3xx HTTP redirect responses.
 *
 * This represents cases where the server indicates that the request
 * should be retried at a different location, but automatic redirect
 * handling was not successful or not enabled.
 *
 * @since 0.2.0
 */
class RedirectException extends RuntimeException
{
    /**
     * Creates a RedirectException from a redirect response.
     *
     * This method extracts redirect information from the response headers
     * and creates an exception with a descriptive message and status code.
     *
     * @since 0.2.0
     *
     * @param Response $response The HTTP redirect response.
     * @return self
     */
    public static function fromRedirectResponse(Response $response): self
    {
        $statusCode = $response->getStatusCode();
        $statusTexts = [300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 307 => 'Temporary Redirect', 308 => 'Permanent Redirect'];
        if (isset($statusTexts[$statusCode])) {
            $errorMessage = sprintf('%s (%d)', $statusTexts[$statusCode], $statusCode);
        } else {
            $errorMessage = sprintf('Redirect error (%d): Request needs to be retried at a different location', $statusCode);
        }
        // Try to extract the redirect location from headers
        $locationValues = $response->getHeader('Location');
        if ($locationValues !== null && !empty($locationValues)) {
            $location = $locationValues[0];
            $errorMessage .= ' - Location: ' . $location;
        }
        return new self($errorMessage, $statusCode);
    }
}
PK�e]���:�	�	,Providers/Http/Exception/ClientException.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Http\Exception;

use WordPress\AiClient\Common\Exception\InvalidArgumentException;
use WordPress\AiClient\Providers\Http\DTO\Request;
use WordPress\AiClient\Providers\Http\DTO\Response;
use WordPress\AiClient\Providers\Http\Util\ErrorMessageExtractor;
/**
 * Exception thrown for 4xx HTTP client errors.
 *
 * This represents errors where the client request was malformed,
 * unauthorized, forbidden, or otherwise invalid.
 *
 * @since 0.2.0
 */
class ClientException extends InvalidArgumentException
{
    /**
     * The request that failed.
     *
     * @var Request|null
     */
    protected ?Request $request = null;
    /**
     * Returns the request that failed as our Request DTO.
     *
     * @since 0.2.0
     *
     * @return Request
     * @throws \RuntimeException If no request is available
     */
    public function getRequest(): Request
    {
        if ($this->request === null) {
            throw new \RuntimeException('Request object not available. This exception was directly instantiated. ' . 'Use a factory method that provides request context.');
        }
        return $this->request;
    }
    /**
     * Creates a ClientException from a client error response (4xx).
     *
     * This method extracts error details from common API response formats
     * and creates an exception with a descriptive message and status code.
     *
     * @since 0.2.0
     *
     * @param Response $response The HTTP response that failed.
     * @return self
     */
    public static function fromClientErrorResponse(Response $response): self
    {
        $statusCode = $response->getStatusCode();
        $statusTexts = [400 => 'Bad Request', 401 => 'Unauthorized', 403 => 'Forbidden', 404 => 'Not Found', 422 => 'Unprocessable Entity', 429 => 'Too Many Requests'];
        if (isset($statusTexts[$statusCode])) {
            $errorMessage = sprintf('%s (%d)', $statusTexts[$statusCode], $statusCode);
        } else {
            $errorMessage = sprintf('Client error (%d): Request was rejected due to client-side issue', $statusCode);
        }
        // Extract error message from response data using centralized utility
        $extractedError = ErrorMessageExtractor::extractFromResponseData($response->getData());
        if ($extractedError !== null) {
            $errorMessage .= ' - ' . $extractedError;
        }
        return new self($errorMessage, $statusCode);
    }
}
PK�e]��JJ-Providers/Http/Exception/NetworkException.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Http\Exception;

use WordPress\AiClientDependencies\Psr\Http\Message\RequestInterface;
use WordPress\AiClient\Common\Exception\RuntimeException;
use WordPress\AiClient\Providers\Http\DTO\Request;
/**
 * Exception thrown for network-related errors.
 *
 * This includes HTTP transport errors, connection failures,
 * timeouts, and other network-related issues.
 *
 * @since 0.2.0
 */
class NetworkException extends RuntimeException
{
    /**
     * The request that failed.
     *
     * @var Request|null
     */
    protected ?Request $request = null;
    /**
     * Returns the request that failed as our Request DTO.
     *
     * @since 0.2.0
     *
     * @return Request
     * @throws \RuntimeException If no request is available
     */
    public function getRequest(): Request
    {
        if ($this->request === null) {
            throw new \RuntimeException('Request object not available. This exception was directly instantiated. ' . 'Use a factory method that provides request context.');
        }
        return $this->request;
    }
    /**
     * Creates a NetworkException from a PSR-18 network exception.
     *
     * @since 0.2.0
     *
     * @param RequestInterface $psrRequest The PSR-7 request that failed.
     * @param \Throwable $networkException The PSR-18 network exception.
     * @return self
     */
    public static function fromPsr18NetworkException(RequestInterface $psrRequest, \Throwable $networkException): self
    {
        $request = Request::fromPsrRequest($psrRequest);
        $message = sprintf('Network error occurred while sending request to %s: %s', $request->getUri(), $networkException->getMessage());
        $exception = new self($message, 0, $networkException);
        $exception->request = $request;
        return $exception;
    }
}
PK�e]7���!!.Providers/Http/Exception/ResponseException.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Http\Exception;

use WordPress\AiClient\Common\Exception\RuntimeException;
/**
 * Exception class for HTTP response errors.
 *
 * This is used when response data is unexpected or malformed,
 * typically indicating that a provider changed in ways our code
 * is not aware of or when parsing response data fails.
 *
 * @since 0.1.0
 */
class ResponseException extends RuntimeException
{
    /**
     * Creates a ResponseException for missing expected data.
     *
     * @since 0.2.0
     *
     * @param string $apiName The name of the API/provider.
     * @param string $fieldName The field that was expected but missing.
     * @return self
     */
    public static function fromMissingData(string $apiName, string $fieldName): self
    {
        $message = sprintf('Unexpected %s API response: Missing the "%s" key.', $apiName, $fieldName);
        return new self($message);
    }
    /**
     * Creates a ResponseException from invalid data in an API response.
     *
     * @since 0.2.0
     *
     * @param string $apiName The name of the API service (e.g., 'OpenAI', 'Anthropic').
     * @param string $fieldName The field that was invalid.
     * @param string $message The specific error message describing the invalid data.
     * @return self
     */
    public static function fromInvalidData(string $apiName, string $fieldName, string $message): self
    {
        return new self(sprintf('Unexpected %s API response: Invalid "%s" key: %s', $apiName, $fieldName, $message));
    }
}
PK�e]����Providers/Http/DTO/Response.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Http\DTO;

use WordPress\AiClient\Common\AbstractDataTransferObject;
use WordPress\AiClient\Common\Exception\InvalidArgumentException;
use WordPress\AiClient\Providers\Http\Collections\HeadersCollection;
/**
 * Represents an HTTP response.
 *
 * This class encapsulates HTTP response data that has been converted
 * from PSR-7 responses by the HTTP transporter.
 *
 * @since 0.1.0
 *
 * @phpstan-type ResponseArrayShape array{
 *     statusCode: int,
 *     headers: array<string, list<string>>,
 *     body?: string|null
 * }
 *
 * @extends AbstractDataTransferObject<ResponseArrayShape>
 */
class Response extends AbstractDataTransferObject
{
    public const KEY_STATUS_CODE = 'statusCode';
    public const KEY_HEADERS = 'headers';
    public const KEY_BODY = 'body';
    /**
     * @var int The HTTP status code.
     */
    protected int $statusCode;
    /**
     * @var HeadersCollection The response headers.
     */
    protected HeadersCollection $headers;
    /**
     * @var string|null The response body.
     */
    protected ?string $body;
    /**
     * Constructor.
     *
     * @since 0.1.0
     *
     * @param int $statusCode The HTTP status code.
     * @param array<string, string|list<string>> $headers The response headers.
     * @param string|null $body The response body.
     *
     * @throws InvalidArgumentException If the status code is invalid.
     */
    public function __construct(int $statusCode, array $headers, ?string $body = null)
    {
        if ($statusCode < 100 || $statusCode >= 600) {
            throw new InvalidArgumentException('Invalid HTTP status code: ' . $statusCode);
        }
        $this->statusCode = $statusCode;
        $this->headers = new HeadersCollection($headers);
        $this->body = $body;
    }
    /**
     * Creates a deep clone of this response.
     *
     * Clones the headers collection to ensure the cloned
     * response is independent of the original.
     *
     * @since 0.4.2
     */
    public function __clone()
    {
        // Clone headers collection
        $this->headers = clone $this->headers;
    }
    /**
     * Gets the HTTP status code.
     *
     * @since 0.1.0
     *
     * @return int The status code.
     */
    public function getStatusCode(): int
    {
        return $this->statusCode;
    }
    /**
     * Gets the response headers.
     *
     * @since 0.1.0
     *
     * @return array<string, list<string>> The headers.
     */
    public function getHeaders(): array
    {
        return $this->headers->getAll();
    }
    /**
     * Gets a specific header value.
     *
     * @since 0.1.0
     *
     * @param string $name The header name (case-insensitive).
     * @return list<string>|null The header value(s) or null if not found.
     */
    public function getHeader(string $name): ?array
    {
        return $this->headers->get($name);
    }
    /**
     * Gets header values as a comma-separated string.
     *
     * @since 0.1.0
     *
     * @param string $name The header name (case-insensitive).
     * @return string|null The header values as a comma-separated string or null if not found.
     */
    public function getHeaderAsString(string $name): ?string
    {
        return $this->headers->getAsString($name);
    }
    /**
     * Gets the response body.
     *
     * @since 0.1.0
     *
     * @return string|null The body.
     */
    public function getBody(): ?string
    {
        return $this->body;
    }
    /**
     * Checks if the response has a header.
     *
     * @since 0.1.0
     *
     * @param string $name The header name.
     * @return bool True if the header exists, false otherwise.
     */
    public function hasHeader(string $name): bool
    {
        return $this->headers->has($name);
    }
    /**
     * Checks if the response indicates success.
     *
     * @since 0.1.0
     *
     * @return bool True if status code is 2xx, false otherwise.
     */
    public function isSuccessful(): bool
    {
        return $this->statusCode >= 200 && $this->statusCode < 300;
    }
    /**
     * Gets the response data as an array.
     *
     * Attempts to decode the body as JSON. Returns null if the body
     * is empty or not valid JSON.
     *
     * @since 0.1.0
     *
     * @return array<string, mixed>|null The decoded data or null.
     */
    public function getData(): ?array
    {
        if ($this->body === null || $this->body === '') {
            return null;
        }
        $data = json_decode($this->body, \true);
        if (json_last_error() !== \JSON_ERROR_NONE) {
            return null;
        }
        /** @var array<string, mixed>|null $data */
        return is_array($data) ? $data : null;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function getJsonSchema(): array
    {
        return ['type' => 'object', 'properties' => [self::KEY_STATUS_CODE => ['type' => 'integer', 'minimum' => 100, 'maximum' => 599, 'description' => 'The HTTP status code.'], self::KEY_HEADERS => ['type' => 'object', 'additionalProperties' => ['type' => 'array', 'items' => ['type' => 'string']], 'description' => 'The response headers.'], self::KEY_BODY => ['type' => ['string', 'null'], 'description' => 'The response body.']], 'required' => [self::KEY_STATUS_CODE, self::KEY_HEADERS]];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     *
     * @return ResponseArrayShape
     */
    public function toArray(): array
    {
        $data = [self::KEY_STATUS_CODE => $this->statusCode, self::KEY_HEADERS => $this->headers->getAll()];
        if ($this->body !== null) {
            $data[self::KEY_BODY] = $this->body;
        }
        return $data;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function fromArray(array $array): self
    {
        static::validateFromArrayData($array, [self::KEY_STATUS_CODE, self::KEY_HEADERS]);
        return new self($array[self::KEY_STATUS_CODE], $array[self::KEY_HEADERS], $array[self::KEY_BODY] ?? null);
    }
}
PK�e]��z�SS%Providers/Http/DTO/RequestOptions.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Http\DTO;

use WordPress\AiClient\Common\AbstractDataTransferObject;
use WordPress\AiClient\Common\Exception\InvalidArgumentException;
/**
 * Represents optional HTTP transport configuration for a single request.
 *
 * Provides mutable setters for working with timeouts and redirect handling.
 *
 * @since 0.2.0
 *
 * @phpstan-type RequestOptionsArrayShape array{
 *     timeout?: float|null,
 *     connectTimeout?: float|null,
 *     maxRedirects?: int|null
 * }
 *
 * @extends AbstractDataTransferObject<RequestOptionsArrayShape>
 */
class RequestOptions extends AbstractDataTransferObject
{
    public const KEY_TIMEOUT = 'timeout';
    public const KEY_CONNECT_TIMEOUT = 'connectTimeout';
    public const KEY_MAX_REDIRECTS = 'maxRedirects';
    /**
     * @var float|null Maximum duration in seconds to wait for the full response.
     */
    protected ?float $timeout = null;
    /**
     * @var float|null Maximum duration in seconds to wait for the initial connection.
     */
    protected ?float $connectTimeout = null;
    /**
     * @var int|null Maximum number of redirects to follow. 0 disables redirects, null is unspecified.
     */
    protected ?int $maxRedirects = null;
    /**
     * Sets the request timeout in seconds.
     *
     * @since 0.2.0
     *
     * @param float|null $timeout Timeout in seconds.
     * @return void
     *
     * @throws InvalidArgumentException When timeout is negative.
     */
    public function setTimeout(?float $timeout): void
    {
        $this->validateTimeout($timeout, self::KEY_TIMEOUT);
        $this->timeout = $timeout;
    }
    /**
     * Sets the connection timeout in seconds.
     *
     * @since 0.2.0
     *
     * @param float|null $timeout Connection timeout in seconds.
     * @return void
     *
     * @throws InvalidArgumentException When timeout is negative.
     */
    public function setConnectTimeout(?float $timeout): void
    {
        $this->validateTimeout($timeout, self::KEY_CONNECT_TIMEOUT);
        $this->connectTimeout = $timeout;
    }
    /**
     * Sets the maximum number of redirects to follow.
     *
     * Set to 0 to disable redirects, null for unspecified, or a positive integer
     * to enable redirects with a maximum count.
     *
     * @since 0.2.0
     *
     * @param int|null $maxRedirects Maximum redirects to follow, or 0 to disable, or null for unspecified.
     * @return void
     *
     * @throws InvalidArgumentException When redirect count is negative.
     */
    public function setMaxRedirects(?int $maxRedirects): void
    {
        if ($maxRedirects !== null && $maxRedirects < 0) {
            throw new InvalidArgumentException('Request option "maxRedirects" must be greater than or equal to 0.');
        }
        $this->maxRedirects = $maxRedirects;
    }
    /**
     * Gets the request timeout in seconds.
     *
     * @since 0.2.0
     *
     * @return float|null Timeout in seconds.
     */
    public function getTimeout(): ?float
    {
        return $this->timeout;
    }
    /**
     * Gets the connection timeout in seconds.
     *
     * @since 0.2.0
     *
     * @return float|null Connection timeout in seconds.
     */
    public function getConnectTimeout(): ?float
    {
        return $this->connectTimeout;
    }
    /**
     * Checks whether redirects are allowed.
     *
     * @since 0.2.0
     *
     * @return bool|null True when redirects are allowed (maxRedirects > 0),
     *                   false when disabled (maxRedirects = 0),
     *                   null when unspecified (maxRedirects = null).
     */
    public function allowsRedirects(): ?bool
    {
        if ($this->maxRedirects === null) {
            return null;
        }
        return $this->maxRedirects > 0;
    }
    /**
     * Gets the maximum number of redirects to follow.
     *
     * @since 0.2.0
     *
     * @return int|null Maximum redirects or null when not specified.
     */
    public function getMaxRedirects(): ?int
    {
        return $this->maxRedirects;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.2.0
     *
     * @return RequestOptionsArrayShape
     */
    public function toArray(): array
    {
        $data = [];
        if ($this->timeout !== null) {
            $data[self::KEY_TIMEOUT] = $this->timeout;
        }
        if ($this->connectTimeout !== null) {
            $data[self::KEY_CONNECT_TIMEOUT] = $this->connectTimeout;
        }
        if ($this->maxRedirects !== null) {
            $data[self::KEY_MAX_REDIRECTS] = $this->maxRedirects;
        }
        return $data;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.2.0
     */
    public static function fromArray(array $array): self
    {
        $instance = new self();
        if (isset($array[self::KEY_TIMEOUT])) {
            $instance->setTimeout((float) $array[self::KEY_TIMEOUT]);
        }
        if (isset($array[self::KEY_CONNECT_TIMEOUT])) {
            $instance->setConnectTimeout((float) $array[self::KEY_CONNECT_TIMEOUT]);
        }
        if (isset($array[self::KEY_MAX_REDIRECTS])) {
            $instance->setMaxRedirects((int) $array[self::KEY_MAX_REDIRECTS]);
        }
        return $instance;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.2.0
     */
    public static function getJsonSchema(): array
    {
        return ['type' => 'object', 'properties' => [self::KEY_TIMEOUT => ['type' => ['number', 'null'], 'minimum' => 0, 'description' => 'Maximum duration in seconds to wait for the full response.'], self::KEY_CONNECT_TIMEOUT => ['type' => ['number', 'null'], 'minimum' => 0, 'description' => 'Maximum duration in seconds to wait for the initial connection.'], self::KEY_MAX_REDIRECTS => ['type' => ['integer', 'null'], 'minimum' => 0, 'description' => 'Maximum redirects to follow. 0 disables, null is unspecified.']], 'additionalProperties' => \false];
    }
    /**
     * Validates timeout values.
     *
     * @since 0.2.0
     *
     * @param float|null $value Timeout to validate.
     * @param string $fieldName Field name for the error message.
     *
     * @throws InvalidArgumentException When timeout is negative.
     */
    private function validateTimeout(?float $value, string $fieldName): void
    {
        if ($value !== null && $value < 0) {
            throw new InvalidArgumentException(sprintf('Request option "%s" must be greater than or equal to 0.', $fieldName));
        }
    }
}
PK�e]�`�h'0'0Providers/Http/DTO/Request.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Http\DTO;

use JsonException;
use WordPress\AiClientDependencies\Psr\Http\Message\RequestInterface;
use WordPress\AiClient\Common\AbstractDataTransferObject;
use WordPress\AiClient\Common\Exception\InvalidArgumentException;
use WordPress\AiClient\Providers\Http\Collections\HeadersCollection;
use WordPress\AiClient\Providers\Http\Enums\HttpMethodEnum;
/**
 * Represents an HTTP request.
 *
 * This class encapsulates HTTP request data that can be converted
 * to PSR-7 requests by the HTTP transporter.
 *
 * @since 0.1.0
 *
 * @phpstan-import-type RequestOptionsArrayShape from RequestOptions
 * @phpstan-type RequestArrayShape array{
 *     method: string,
 *     uri: string,
 *     headers: array<string, list<string>>,
 *     body?: string|null,
 *     options?: RequestOptionsArrayShape
 * }
 *
 * @extends AbstractDataTransferObject<RequestArrayShape>
 */
class Request extends AbstractDataTransferObject
{
    public const KEY_METHOD = 'method';
    public const KEY_URI = 'uri';
    public const KEY_HEADERS = 'headers';
    public const KEY_BODY = 'body';
    public const KEY_OPTIONS = 'options';
    /**
     * @var HttpMethodEnum The HTTP method.
     */
    protected HttpMethodEnum $method;
    /**
     * @var string The request URI.
     */
    protected string $uri;
    /**
     * @var HeadersCollection The request headers.
     */
    protected HeadersCollection $headers;
    /**
     * @var array<string, mixed>|null The request data (for query params or form data).
     */
    protected ?array $data = null;
    /**
     * @var string|null The request body (raw string content).
     */
    protected ?string $body = null;
    /**
     * @var RequestOptions|null Request transport options.
     */
    protected ?\WordPress\AiClient\Providers\Http\DTO\RequestOptions $options = null;
    /**
     * Constructor.
     *
     * @since 0.1.0
     *
     * @param HttpMethodEnum $method The HTTP method.
     * @param string $uri The request URI.
     * @param array<string, string|list<string>> $headers The request headers.
     * @param string|array<string, mixed>|null $data The request data.
     * @param RequestOptions|null $options The request transport options.
     *
     * @throws InvalidArgumentException If the URI is empty.
     */
    public function __construct(HttpMethodEnum $method, string $uri, array $headers = [], $data = null, ?\WordPress\AiClient\Providers\Http\DTO\RequestOptions $options = null)
    {
        if (empty($uri)) {
            throw new InvalidArgumentException('URI cannot be empty.');
        }
        $this->method = $method;
        $this->uri = $uri;
        $this->headers = new HeadersCollection($headers);
        // Separate data and body based on type
        if (is_string($data)) {
            $this->body = $data;
        } elseif (is_array($data)) {
            $this->data = $data;
        }
        $this->options = $options;
    }
    /**
     * Creates a deep clone of this request.
     *
     * Clones the headers collection and request options to ensure
     * the cloned request is independent of the original.
     * The HTTP method enum is immutable and can be safely shared.
     *
     * @since 0.4.2
     */
    public function __clone()
    {
        // Clone headers collection
        $this->headers = clone $this->headers;
        // Clone request options if present (contains only primitives)
        if ($this->options !== null) {
            $this->options = clone $this->options;
        }
        // Note: $method is an immutable enum and can be safely shared
    }
    /**
     * Gets the HTTP method.
     *
     * @since 0.1.0
     *
     * @return HttpMethodEnum The HTTP method.
     */
    public function getMethod(): HttpMethodEnum
    {
        return $this->method;
    }
    /**
     * Gets the request URI.
     *
     * For GET requests with array data, appends the data as query parameters.
     *
     * @since 0.1.0
     *
     * @return string The URI.
     */
    public function getUri(): string
    {
        // If GET request with data, append as query parameters
        if ($this->method === HttpMethodEnum::GET() && $this->data !== null && !empty($this->data)) {
            $separator = str_contains($this->uri, '?') ? '&' : '?';
            return $this->uri . $separator . http_build_query($this->data);
        }
        return $this->uri;
    }
    /**
     * Gets the request headers.
     *
     * @since 0.1.0
     *
     * @return array<string, list<string>> The headers.
     */
    public function getHeaders(): array
    {
        return $this->headers->getAll();
    }
    /**
     * Gets a specific header value.
     *
     * @since 0.1.0
     *
     * @param string $name The header name (case-insensitive).
     * @return list<string>|null The header value(s) or null if not found.
     */
    public function getHeader(string $name): ?array
    {
        return $this->headers->get($name);
    }
    /**
     * Gets header values as a comma-separated string.
     *
     * @since 0.1.0
     *
     * @param string $name The header name (case-insensitive).
     * @return string|null The header values as a comma-separated string, or null if not found.
     */
    public function getHeaderAsString(string $name): ?string
    {
        return $this->headers->getAsString($name);
    }
    /**
     * Checks if a header exists.
     *
     * @since 0.1.0
     *
     * @param string $name The header name (case-insensitive).
     * @return bool True if the header exists, false otherwise.
     */
    public function hasHeader(string $name): bool
    {
        return $this->headers->has($name);
    }
    /**
     * Gets the request body.
     *
     * For GET requests, returns null.
     * For POST/PUT/PATCH requests:
     * - If body is set, returns it as-is
     * - If data is set and Content-Type is JSON, returns JSON-encoded data
     * - If data is set and Content-Type is form, returns URL-encoded data
     *
     * @since 0.1.0
     *
     * @return string|null The body.
     * @throws JsonException If the data cannot be encoded to JSON.
     */
    public function getBody(): ?string
    {
        // GET requests don't have a body
        if (!$this->method->hasBody()) {
            return null;
        }
        // If body is set, return it as-is
        if ($this->body !== null) {
            return $this->body;
        }
        // If data is set, encode based on content type
        if ($this->data !== null) {
            $contentType = $this->getContentType();
            // JSON encoding
            if ($contentType !== null && stripos($contentType, 'application/json') !== \false) {
                return json_encode($this->data, \JSON_THROW_ON_ERROR);
            }
            // Default to URL encoding for forms
            return http_build_query($this->data);
        }
        return null;
    }
    /**
     * Gets the Content-Type header value.
     *
     * @since 0.1.0
     *
     * @return string|null The Content-Type header value or null if not set.
     */
    private function getContentType(): ?string
    {
        $values = $this->getHeader('Content-Type');
        return $values !== null ? $values[0] : null;
    }
    /**
     * Returns a new instance with the specified header.
     *
     * @since 0.1.0
     *
     * @param string $name The header name.
     * @param string|list<string> $value The header value(s).
     * @return self A new instance with the header.
     */
    public function withHeader(string $name, $value): self
    {
        $newHeaders = $this->headers->withHeader($name, $value);
        $new = clone $this;
        $new->headers = $newHeaders;
        return $new;
    }
    /**
     * Returns a new instance with the specified data.
     *
     * @since 0.1.0
     *
     * @param string|array<string, mixed> $data The request data.
     * @return self A new instance with the data.
     */
    public function withData($data): self
    {
        $new = clone $this;
        if (is_string($data)) {
            $new->body = $data;
            $new->data = null;
        } elseif (is_array($data)) {
            $new->data = $data;
            $new->body = null;
        } else {
            $new->data = null;
            $new->body = null;
        }
        return $new;
    }
    /**
     * Gets the request data array.
     *
     * @since 0.1.0
     *
     * @return array<string, mixed>|null The request data array.
     */
    public function getData(): ?array
    {
        return $this->data;
    }
    /**
     * Gets the request options.
     *
     * @since 0.2.0
     *
     * @return RequestOptions|null Request transport options when configured.
     */
    public function getOptions(): ?\WordPress\AiClient\Providers\Http\DTO\RequestOptions
    {
        return $this->options;
    }
    /**
     * Returns a new instance with the specified request options.
     *
     * @since 0.2.0
     *
     * @param RequestOptions|null $options The request options to apply.
     * @return self A new instance with the options.
     */
    public function withOptions(?\WordPress\AiClient\Providers\Http\DTO\RequestOptions $options): self
    {
        $new = clone $this;
        $new->options = $options;
        return $new;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function getJsonSchema(): array
    {
        return ['type' => 'object', 'properties' => [self::KEY_METHOD => ['type' => 'string', 'description' => 'The HTTP method.'], self::KEY_URI => ['type' => 'string', 'description' => 'The request URI.'], self::KEY_HEADERS => ['type' => 'object', 'additionalProperties' => ['type' => 'array', 'items' => ['type' => 'string']], 'description' => 'The request headers.'], self::KEY_BODY => ['type' => ['string'], 'description' => 'The request body.'], self::KEY_OPTIONS => \WordPress\AiClient\Providers\Http\DTO\RequestOptions::getJsonSchema()], 'required' => [self::KEY_METHOD, self::KEY_URI, self::KEY_HEADERS]];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     *
     * @return RequestArrayShape
     */
    public function toArray(): array
    {
        $array = [
            self::KEY_METHOD => $this->method->value,
            self::KEY_URI => $this->getUri(),
            // Include query params if GET with data
            self::KEY_HEADERS => $this->headers->getAll(),
        ];
        // Include body if present (getBody() handles the conversion)
        $body = $this->getBody();
        if ($body !== null) {
            $array[self::KEY_BODY] = $body;
        }
        if ($this->options !== null) {
            $optionsArray = $this->options->toArray();
            if (!empty($optionsArray)) {
                $array[self::KEY_OPTIONS] = $optionsArray;
            }
        }
        return $array;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function fromArray(array $array): self
    {
        static::validateFromArrayData($array, [self::KEY_METHOD, self::KEY_URI, self::KEY_HEADERS]);
        return new self(HttpMethodEnum::from($array[self::KEY_METHOD]), $array[self::KEY_URI], $array[self::KEY_HEADERS] ?? [], $array[self::KEY_BODY] ?? null, isset($array[self::KEY_OPTIONS]) ? \WordPress\AiClient\Providers\Http\DTO\RequestOptions::fromArray($array[self::KEY_OPTIONS]) : null);
    }
    /**
     * Creates a Request instance from a PSR-7 RequestInterface.
     *
     * @since 0.2.0
     *
     * @param RequestInterface $psrRequest The PSR-7 request to convert.
     * @return self A new Request instance.
     * @throws InvalidArgumentException If the HTTP method is not supported.
     */
    public static function fromPsrRequest(RequestInterface $psrRequest): self
    {
        $method = HttpMethodEnum::from($psrRequest->getMethod());
        $uri = (string) $psrRequest->getUri();
        // Convert PSR-7 headers to array format expected by our constructor
        /** @var array<string, list<string>> $headers */
        $headers = $psrRequest->getHeaders();
        // Get body content
        $body = $psrRequest->getBody()->getContents();
        $bodyOrData = !empty($body) ? $body : null;
        return new self($method, $uri, $headers, $bodyOrData);
    }
}
PK�e]=���O	O	2Providers/Http/DTO/ApiKeyRequestAuthentication.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Http\DTO;

use WordPress\AiClient\Common\AbstractDataTransferObject;
use WordPress\AiClient\Providers\Http\Contracts\RequestAuthenticationInterface;
/**
 * Class for HTTP request authentication using an API key.
 *
 * @since 0.1.0
 *
 * @phpstan-type ApiKeyRequestAuthenticationArrayShape array{
 *     apiKey: string
 * }
 *
 * @extends AbstractDataTransferObject<ApiKeyRequestAuthenticationArrayShape>
 */
class ApiKeyRequestAuthentication extends AbstractDataTransferObject implements RequestAuthenticationInterface
{
    public const KEY_API_KEY = 'apiKey';
    /**
     * @var string The API key used for authentication.
     */
    protected string $apiKey;
    /**
     * Constructor.
     *
     * @since 0.1.0
     *
     * @param string $apiKey The API key used for authentication.
     */
    public function __construct(string $apiKey)
    {
        $this->apiKey = $apiKey;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public function authenticateRequest(\WordPress\AiClient\Providers\Http\DTO\Request $request): \WordPress\AiClient\Providers\Http\DTO\Request
    {
        // Add the API key to the request headers.
        return $request->withHeader('Authorization', 'Bearer ' . $this->apiKey);
    }
    /**
     * Gets the API key.
     *
     * @since 0.1.0
     *
     * @return string The API key.
     */
    public function getApiKey(): string
    {
        return $this->apiKey;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     *
     * @since 0.1.0
     *
     * @return ApiKeyRequestAuthenticationArrayShape
     */
    public function toArray(): array
    {
        return [self::KEY_API_KEY => $this->apiKey];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     *
     * @since 0.1.0
     */
    public static function fromArray(array $array): self
    {
        static::validateFromArrayData($array, [self::KEY_API_KEY]);
        return new self($array[self::KEY_API_KEY]);
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function getJsonSchema(): array
    {
        return ['type' => 'object', 'properties' => [self::KEY_API_KEY => ['type' => 'string', 'title' => 'API Key', 'description' => 'The API key used for authentication.']], 'required' => [self::KEY_API_KEY]];
    }
}
PK�e]�녬ff$Providers/Http/Util/ResponseUtil.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Http\Util;

use WordPress\AiClient\Providers\Http\DTO\Response;
use WordPress\AiClient\Providers\Http\Exception\ClientException;
use WordPress\AiClient\Providers\Http\Exception\RedirectException;
use WordPress\AiClient\Providers\Http\Exception\ServerException;
/**
 * Class with static utility methods to process HTTP responses.
 *
 * @since 0.1.0
 */
class ResponseUtil
{
    /**
     * Throws an appropriate exception if the given response is not successful.
     *
     * This method checks the HTTP status code of the response and throws
     * the appropriate exception type based on the status code range:
     * - 3xx: RedirectException (redirect responses)
     * - 4xx: ClientException (client errors)
     * - 5xx: ServerException (server errors)
     * - Other unsuccessful responses: RuntimeException (invalid status codes)
     *
     * @since 0.1.0
     *
     * @param Response $response The HTTP response to check.
     * @throws RedirectException If the response indicates a redirect (3xx).
     * @throws ClientException If the response indicates a client error (4xx).
     * @throws ServerException If the response indicates a server error (5xx).
     * @throws \RuntimeException If the response has an invalid status code.
     */
    public static function throwIfNotSuccessful(Response $response): void
    {
        if ($response->isSuccessful()) {
            return;
        }
        $statusCode = $response->getStatusCode();
        // 3xx Redirect Responses
        if ($statusCode >= 300 && $statusCode < 400) {
            throw RedirectException::fromRedirectResponse($response);
        }
        // 4xx Client Errors
        if ($statusCode >= 400 && $statusCode < 500) {
            throw ClientException::fromClientErrorResponse($response);
        }
        // 5xx Server Errors
        if ($statusCode >= 500 && $statusCode < 600) {
            throw ServerException::fromServerErrorResponse($response);
        }
        throw new \RuntimeException(sprintf('Response returned invalid status code: %s', $response->getStatusCode()));
    }
}
PK�e]�q��ee-Providers/Http/Util/ErrorMessageExtractor.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Http\Util;

/**
 * Utility for extracting error messages from API response data.
 *
 * Centralizes the logic for parsing common API error response formats
 * to avoid code duplication across exception classes.
 *
 * @since 0.2.0
 * @since 0.4.0 Moved from Utilities namespace to Util namespace.
 */
class ErrorMessageExtractor
{
    /**
     * Extracts error message from API response data.
     *
     * Handles common error response formats:
     * - { "error": { "message": "Error text" } }
     * - { "error": "Error text" }
     * - { "message": "Error text" }
     *
     * @since 0.2.0
     *
     * @param mixed $data The response data to extract error message from.
     * @return string|null The extracted error message, or null if none found.
     */
    public static function extractFromResponseData($data): ?string
    {
        if (!is_array($data)) {
            return null;
        }
        // Handle [ { "error": { "message": "Error text" } } ]
        if (isset($data[0]) && is_array($data[0]) && isset($data[0]['error']) && is_array($data[0]['error']) && isset($data[0]['error']['message']) && is_string($data[0]['error']['message'])) {
            return $data[0]['error']['message'];
        }
        // Handle { "error": { "message": "Error text" } }
        if (isset($data['error']) && is_array($data['error']) && isset($data['error']['message']) && is_string($data['error']['message'])) {
            return $data['error']['message'];
        }
        // Handle { "error": "Error text" }
        if (isset($data['error']) && is_string($data['error'])) {
            return $data['error'];
        }
        // Handle { "message": "Error text" }
        if (isset($data['message']) && is_string($data['message'])) {
            return $data['message'];
        }
        return null;
    }
}
PK�e]z�G		0Providers/Http/Collections/HeadersCollection.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Http\Collections;

/**
 * Simple collection for managing HTTP headers with case-insensitive access.
 *
 * This class stores HTTP headers while preserving their original casing
 * and provides efficient case-insensitive lookups.
 *
 * @since 0.1.0
 */
class HeadersCollection
{
    /**
     * @var array<string, list<string>> The headers with original casing.
     */
    private array $headers = [];
    /**
     * @var array<string, string> Map of lowercase header names to actual header names.
     */
    private array $headersMap = [];
    /**
     * Constructor.
     *
     * @since 0.1.0
     *
     * @param array<string, string|list<string>> $headers Initial headers.
     */
    public function __construct(array $headers = [])
    {
        foreach ($headers as $name => $value) {
            $this->set($name, $value);
        }
    }
    /**
     * Gets a specific header value.
     *
     * @since 0.1.0
     *
     * @param string $name The header name (case-insensitive).
     * @return list<string>|null The header value(s) or null if not found.
     */
    public function get(string $name): ?array
    {
        $lowerName = strtolower($name);
        if (!isset($this->headersMap[$lowerName])) {
            return null;
        }
        $actualName = $this->headersMap[$lowerName];
        return $this->headers[$actualName];
    }
    /**
     * Gets all headers.
     *
     * @since 0.1.0
     *
     * @return array<string, list<string>> All headers with their original casing.
     */
    public function getAll(): array
    {
        return $this->headers;
    }
    /**
     * Gets header values as a comma-separated string.
     *
     * @since 0.1.0
     *
     * @param string $name The header name (case-insensitive).
     * @return string|null The header values as a comma-separated string or null if not found.
     */
    public function getAsString(string $name): ?string
    {
        $values = $this->get($name);
        return $values !== null ? implode(', ', $values) : null;
    }
    /**
     * Checks if a header exists.
     *
     * @since 0.1.0
     *
     * @param string $name The header name (case-insensitive).
     * @return bool True if the header exists, false otherwise.
     */
    public function has(string $name): bool
    {
        return isset($this->headersMap[strtolower($name)]);
    }
    /**
     * Sets a header value, replacing any existing value.
     *
     * @since 0.1.0
     *
     * @param string $name The header name.
     * @param string|list<string> $value The header value(s).
     * @return void
     */
    private function set(string $name, $value): void
    {
        if (is_array($value)) {
            $normalizedValues = array_values($value);
        } else {
            // Split comma-separated string into array
            $normalizedValues = array_map('trim', explode(',', $value));
        }
        $lowerName = strtolower($name);
        // If header exists with different casing, remove the old casing
        if (isset($this->headersMap[$lowerName])) {
            $oldName = $this->headersMap[$lowerName];
            if ($oldName !== $name) {
                unset($this->headers[$oldName]);
            }
        }
        // Always use the new casing
        $this->headers[$name] = $normalizedValues;
        $this->headersMap[$lowerName] = $name;
    }
    /**
     * Returns a new instance with the specified header.
     *
     * @since 0.1.0
     *
     * @param string $name The header name.
     * @param string|list<string> $value The header value(s).
     * @return self A new instance with the header.
     */
    public function withHeader(string $name, $value): self
    {
        $new = clone $this;
        $new->set($name, $value);
        return $new;
    }
}
PK�e]�^�C�*�*"Providers/Http/HttpTransporter.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Http;

use WordPress\AiClientDependencies\Http\Discovery\Psr17FactoryDiscovery;
use WordPress\AiClientDependencies\Http\Discovery\Psr18ClientDiscovery;
use WordPress\AiClientDependencies\Psr\Http\Client\ClientInterface;
use WordPress\AiClientDependencies\Psr\Http\Message\RequestFactoryInterface;
use WordPress\AiClientDependencies\Psr\Http\Message\RequestInterface;
use WordPress\AiClientDependencies\Psr\Http\Message\ResponseInterface;
use WordPress\AiClientDependencies\Psr\Http\Message\StreamFactoryInterface;
use WordPress\AiClient\Common\Exception\RuntimeException;
use WordPress\AiClient\Providers\Http\Contracts\ClientWithOptionsInterface;
use WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface;
use WordPress\AiClient\Providers\Http\DTO\Request;
use WordPress\AiClient\Providers\Http\DTO\RequestOptions;
use WordPress\AiClient\Providers\Http\DTO\Response;
use WordPress\AiClient\Providers\Http\Exception\NetworkException;
/**
 * HTTP transporter implementation using HTTPlug.
 *
 * This class handles the conversion between custom Request/Response
 * objects and PSR-7 messages, using HTTPlug for client abstraction
 * and PSR-17 factories for message creation.
 *
 * @since 0.1.0
 */
class HttpTransporter implements HttpTransporterInterface
{
    /**
     * @var RequestFactoryInterface PSR-17 request factory.
     */
    private RequestFactoryInterface $requestFactory;
    /**
     * @var StreamFactoryInterface PSR-17 stream factory.
     */
    private StreamFactoryInterface $streamFactory;
    /**
     * @var ClientInterface PSR-18 HTTP client.
     */
    private ClientInterface $client;
    /**
     * Constructor.
     *
     * @since 0.1.0
     *
     * @param ClientInterface|null $client PSR-18 HTTP client.
     * @param RequestFactoryInterface|null $requestFactory PSR-17 request factory.
     * @param StreamFactoryInterface|null $streamFactory PSR-17 stream factory.
     */
    public function __construct(?ClientInterface $client = null, ?RequestFactoryInterface $requestFactory = null, ?StreamFactoryInterface $streamFactory = null)
    {
        $this->client = $client ?: Psr18ClientDiscovery::find();
        $this->requestFactory = $requestFactory ?: Psr17FactoryDiscovery::findRequestFactory();
        $this->streamFactory = $streamFactory ?: Psr17FactoryDiscovery::findStreamFactory();
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     * @since 0.2.0 Added optional RequestOptions parameter and ClientWithOptions support.
     */
    public function send(Request $request, ?RequestOptions $options = null): Response
    {
        $psr7Request = $this->convertToPsr7Request($request);
        // Merge request options with parameter options, with parameter options taking precedence
        $mergedOptions = $this->mergeOptions($request->getOptions(), $options);
        try {
            $hasOptions = $mergedOptions !== null;
            if ($hasOptions && $this->client instanceof ClientWithOptionsInterface) {
                $psr7Response = $this->client->sendRequestWithOptions($psr7Request, $mergedOptions);
            } elseif ($hasOptions && $this->isGuzzleClient($this->client)) {
                $psr7Response = $this->sendWithGuzzle($psr7Request, $mergedOptions);
            } else {
                $psr7Response = $this->client->sendRequest($psr7Request);
            }
        } catch (\WordPress\AiClientDependencies\Psr\Http\Client\NetworkExceptionInterface $e) {
            throw NetworkException::fromPsr18NetworkException($psr7Request, $e);
        } catch (\WordPress\AiClientDependencies\Psr\Http\Client\ClientExceptionInterface $e) {
            // Handle other PSR-18 client exceptions that are not network-related
            throw new RuntimeException(sprintf('HTTP client error occurred while sending request to %s: %s', $request->getUri(), $e->getMessage()), 0, $e);
        }
        return $this->convertFromPsr7Response($psr7Response);
    }
    /**
     * Merges request options with parameter options taking precedence.
     *
     * @since 0.2.0
     *
     * @param RequestOptions|null $requestOptions Options from the Request object.
     * @param RequestOptions|null $parameterOptions Options passed as method parameter.
     * @return RequestOptions|null Merged options, or null if both are null.
     */
    private function mergeOptions(?RequestOptions $requestOptions, ?RequestOptions $parameterOptions): ?RequestOptions
    {
        // If no options at all, return null
        if ($requestOptions === null && $parameterOptions === null) {
            return null;
        }
        // If only one set of options exists, return it
        if ($requestOptions === null) {
            return $parameterOptions;
        }
        if ($parameterOptions === null) {
            return $requestOptions;
        }
        // Both exist, merge them with parameter options taking precedence
        $merged = new RequestOptions();
        // Start with request options (lower precedence)
        if ($requestOptions->getTimeout() !== null) {
            $merged->setTimeout($requestOptions->getTimeout());
        }
        if ($requestOptions->getConnectTimeout() !== null) {
            $merged->setConnectTimeout($requestOptions->getConnectTimeout());
        }
        if ($requestOptions->getMaxRedirects() !== null) {
            $merged->setMaxRedirects($requestOptions->getMaxRedirects());
        }
        // Override with parameter options (higher precedence)
        if ($parameterOptions->getTimeout() !== null) {
            $merged->setTimeout($parameterOptions->getTimeout());
        }
        if ($parameterOptions->getConnectTimeout() !== null) {
            $merged->setConnectTimeout($parameterOptions->getConnectTimeout());
        }
        if ($parameterOptions->getMaxRedirects() !== null) {
            $merged->setMaxRedirects($parameterOptions->getMaxRedirects());
        }
        return $merged;
    }
    /**
     * Determines if the underlying client matches the Guzzle client shape.
     *
     * @since 0.2.0
     *
     * @param ClientInterface $client The HTTP client instance.
     * @return bool True when the client exposes Guzzle's send signature.
     */
    private function isGuzzleClient(ClientInterface $client): bool
    {
        $reflection = new \ReflectionObject($client);
        if (!is_callable([$client, 'send'])) {
            return \false;
        }
        if (!$reflection->hasMethod('send')) {
            return \false;
        }
        $method = $reflection->getMethod('send');
        if (!$method->isPublic() || $method->isStatic()) {
            return \false;
        }
        $parameters = $method->getParameters();
        if (count($parameters) < 2) {
            return \false;
        }
        $firstParameter = $parameters[0]->getType();
        if (!$firstParameter instanceof \ReflectionNamedType || $firstParameter->isBuiltin()) {
            return \false;
        }
        if (!is_a($firstParameter->getName(), RequestInterface::class, \true)) {
            return \false;
        }
        $secondParameter = $parameters[1];
        $secondType = $secondParameter->getType();
        if (!$secondType instanceof \ReflectionNamedType || $secondType->getName() !== 'array') {
            return \false;
        }
        return \true;
    }
    /**
     * Sends a request using a Guzzle-compatible client.
     *
     * @since 0.2.0
     *
     * @param RequestInterface $request The PSR-7 request to send.
     * @param RequestOptions $options The request options.
     * @return ResponseInterface The PSR-7 response received.
     */
    private function sendWithGuzzle(RequestInterface $request, RequestOptions $options): ResponseInterface
    {
        $guzzleOptions = $this->buildGuzzleOptions($options);
        /** @var callable $callable */
        $callable = [$this->client, 'send'];
        /** @var ResponseInterface $response */
        $response = $callable($request, $guzzleOptions);
        return $response;
    }
    /**
     * Converts request options to a Guzzle-compatible options array.
     *
     * @since 0.2.0
     *
     * @param RequestOptions $options The request options.
     * @return array<string, mixed> Guzzle-compatible options.
     */
    private function buildGuzzleOptions(RequestOptions $options): array
    {
        $guzzleOptions = [];
        $timeout = $options->getTimeout();
        if ($timeout !== null) {
            $guzzleOptions['timeout'] = $timeout;
        }
        $connectTimeout = $options->getConnectTimeout();
        if ($connectTimeout !== null) {
            $guzzleOptions['connect_timeout'] = $connectTimeout;
        }
        $allowRedirects = $options->allowsRedirects();
        if ($allowRedirects !== null) {
            if ($allowRedirects) {
                $redirectOptions = [];
                $maxRedirects = $options->getMaxRedirects();
                if ($maxRedirects !== null) {
                    $redirectOptions['max'] = $maxRedirects;
                }
                $guzzleOptions['allow_redirects'] = !empty($redirectOptions) ? $redirectOptions : \true;
            } else {
                $guzzleOptions['allow_redirects'] = \false;
            }
        }
        return $guzzleOptions;
    }
    /**
     * Converts a custom Request to a PSR-7 request.
     *
     * @since 0.1.0
     *
     * @param Request $request The custom request.
     * @return RequestInterface The PSR-7 request.
     */
    private function convertToPsr7Request(Request $request): RequestInterface
    {
        $psr7Request = $this->requestFactory->createRequest($request->getMethod()->value, $request->getUri());
        // Add headers
        foreach ($request->getHeaders() as $name => $values) {
            foreach ($values as $value) {
                $psr7Request = $psr7Request->withAddedHeader($name, $value);
            }
        }
        // Add body if present
        $body = $request->getBody();
        if ($body !== null) {
            $stream = $this->streamFactory->createStream($body);
            $psr7Request = $psr7Request->withBody($stream);
        }
        return $psr7Request;
    }
    /**
     * Converts a PSR-7 response to a custom Response.
     *
     * @since 0.1.0
     *
     * @param ResponseInterface $psr7Response The PSR-7 response.
     * @return Response The custom response.
     */
    private function convertFromPsr7Response(ResponseInterface $psr7Response): Response
    {
        $body = (string) $psr7Response->getBody();
        // PSR-7 always returns headers as arrays, but HeadersCollection handles this
        return new Response(
            $psr7Response->getStatusCode(),
            $psr7Response->getHeaders(),
            // @phpstan-ignore-line
            $body === '' ? null : $body
        );
    }
}
PK�e]��.��4Providers/Http/Enums/RequestAuthenticationMethod.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Http\Enums;

use WordPress\AiClient\Common\AbstractEnum;
use WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface;
use WordPress\AiClient\Providers\Http\Contracts\RequestAuthenticationInterface;
use WordPress\AiClient\Providers\Http\DTO\ApiKeyRequestAuthentication;
/**
 * Enum for request authentication methods.
 *
 * @since 0.4.0
 *
 * @method static self apiKey() Creates an instance for API_KEY method.
 * @method bool isApiKey() Checks if the method is API_KEY.
 */
class RequestAuthenticationMethod extends AbstractEnum
{
    /**
     * API key authentication.
     */
    public const API_KEY = 'api_key';
    /**
     * Gets the implementation class for the authentication method.
     *
     * @since 0.4.0
     *
     * @return class-string<RequestAuthenticationInterface&WithArrayTransformationInterface> The implementation class.
     *
     * @phpstan-ignore missingType.generics
     */
    public function getImplementationClass(): string
    {
        // At the moment, this is the only supported method.
        // Once more methods are available, add conditionals here for each method.
        return ApiKeyRequestAuthentication::class;
    }
}
PK�e]����	�	'Providers/Http/Enums/HttpMethodEnum.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Http\Enums;

use WordPress\AiClient\Common\AbstractEnum;
/**
 * Represents HTTP request methods.
 *
 * @since 0.1.0
 *
 * @method static self GET()
 * @method static self POST()
 * @method static self PUT()
 * @method static self PATCH()
 * @method static self DELETE()
 * @method static self HEAD()
 * @method static self OPTIONS()
 * @method static self CONNECT()
 * @method static self TRACE()
 *
 * @method bool isGet()
 * @method bool isPost()
 * @method bool isPut()
 * @method bool isPatch()
 * @method bool isDelete()
 * @method bool isHead()
 * @method bool isOptions()
 * @method bool isConnect()
 * @method bool isTrace()
 */
final class HttpMethodEnum extends AbstractEnum
{
    /**
     * GET method for retrieving resources.
     *
     * @var string
     */
    public const GET = 'GET';
    /**
     * POST method for creating resources.
     *
     * @var string
     */
    public const POST = 'POST';
    /**
     * PUT method for updating/replacing resources.
     *
     * @var string
     */
    public const PUT = 'PUT';
    /**
     * PATCH method for partially updating resources.
     *
     * @var string
     */
    public const PATCH = 'PATCH';
    /**
     * DELETE method for removing resources.
     *
     * @var string
     */
    public const DELETE = 'DELETE';
    /**
     * HEAD method for retrieving headers only.
     *
     * @var string
     */
    public const HEAD = 'HEAD';
    /**
     * OPTIONS method for retrieving allowed methods.
     *
     * @var string
     */
    public const OPTIONS = 'OPTIONS';
    /**
     * CONNECT method for establishing tunnel.
     *
     * @var string
     */
    public const CONNECT = 'CONNECT';
    /**
     * TRACE method for diagnostic purposes.
     *
     * @var string
     */
    public const TRACE = 'TRACE';
    /**
     * Checks if this method is idempotent.
     *
     * @since 0.1.0
     *
     * @return bool True if the method is idempotent, false otherwise.
     */
    public function isIdempotent(): bool
    {
        return in_array($this->value, [self::GET, self::HEAD, self::OPTIONS, self::TRACE, self::PUT, self::DELETE], \true);
    }
    /**
     * Checks if this method typically has a request body.
     *
     * @since 0.1.0
     *
     * @return bool True if the method typically has a body, false otherwise.
     */
    public function hasBody(): bool
    {
        return in_array($this->value, [self::POST, self::PUT, self::PATCH], \true);
    }
}
PK�e]�"�w7Providers/Http/Contracts/ClientWithOptionsInterface.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Http\Contracts;

use WordPress\AiClientDependencies\Psr\Http\Message\RequestInterface;
use WordPress\AiClientDependencies\Psr\Http\Message\ResponseInterface;
use WordPress\AiClient\Providers\Http\DTO\RequestOptions;
/**
 * Interface for HTTP clients that support per-request transport options.
 *
 * Extends the capabilities of PSR-18 clients by allowing custom transport
 * configuration such as timeouts and redirect handling on each request.
 *
 * @since 0.2.0
 */
interface ClientWithOptionsInterface
{
    /**
     * Sends an HTTP request with the given transport options.
     *
     * @since 0.2.0
     *
     * @param RequestInterface $request The PSR-7 request to send.
     * @param RequestOptions $options The request transport options. Must not be null.
     * @return ResponseInterface The PSR-7 response received.
     */
    public function sendRequestWithOptions(RequestInterface $request, RequestOptions $options): ResponseInterface;
}
PK�e]��M�mm;Providers/Http/Contracts/RequestAuthenticationInterface.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Http\Contracts;

use WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface;
use WordPress\AiClient\Providers\Http\DTO\Request;
/**
 * Interface for HTTP request authentication.
 *
 * @since 0.1.0
 */
interface RequestAuthenticationInterface extends WithJsonSchemaInterface
{
    /**
     * Authenticates an HTTP request.
     *
     * @since 0.1.0
     *
     * @param Request $request The request to authenticate.
     * @return Request The authenticated request.
     */
    public function authenticateRequest(Request $request): Request;
}
PK�e]>H��``?Providers/Http/Contracts/WithRequestAuthenticationInterface.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Http\Contracts;

/**
 * Interface for models that support request authentication.
 *
 * @since 0.1.0
 */
interface WithRequestAuthenticationInterface
{
    /**
     * Sets the request authentication.
     *
     * @since 0.1.0
     *
     * @param RequestAuthenticationInterface $authentication The authentication instance.
     * @return void
     */
    public function setRequestAuthentication(\WordPress\AiClient\Providers\Http\Contracts\RequestAuthenticationInterface $authentication): void;
    /**
     * Returns the request authentication.
     *
     * @since 0.1.0
     *
     * @return RequestAuthenticationInterface The authentication instance.
     */
    public function getRequestAuthentication(): \WordPress\AiClient\Providers\Http\Contracts\RequestAuthenticationInterface;
}
PK�e]��--9Providers/Http/Contracts/WithHttpTransporterInterface.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Http\Contracts;

/**
 * Interface for models that require HTTP transport capabilities.
 *
 * @since 0.1.0
 */
interface WithHttpTransporterInterface
{
    /**
     * Sets the HTTP transporter.
     *
     * @since 0.1.0
     *
     * @param HttpTransporterInterface $transporter The HTTP transporter instance.
     * @return void
     */
    public function setHttpTransporter(\WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface $transporter): void;
    /**
     * Returns the HTTP transporter.
     *
     * @since 0.1.0
     *
     * @return HttpTransporterInterface The HTTP transporter instance.
     */
    public function getHttpTransporter(): \WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface;
}
PK�e]P�dN\\5Providers/Http/Contracts/HttpTransporterInterface.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Http\Contracts;

use WordPress\AiClient\Providers\Http\DTO\Request;
use WordPress\AiClient\Providers\Http\DTO\RequestOptions;
use WordPress\AiClient\Providers\Http\DTO\Response;
/**
 * Interface for HTTP transport implementations.
 *
 * Handles sending HTTP requests and receiving responses using
 * PSR-7, PSR-17, and PSR-18 standards internally.
 *
 * @since 0.1.0
 */
interface HttpTransporterInterface
{
    /**
     * Sends an HTTP request and returns the response.
     *
     * @since 0.1.0
     *
     * @param Request $request The request to send.
     * @param RequestOptions|null $options Optional transport options for the request.
     * @return Response The response received.
     */
    public function send(Request $request, ?RequestOptions $options = null): Response;
}
PK�e]���5��$Providers/Enums/ProviderTypeEnum.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Enums;

use WordPress\AiClient\Common\AbstractEnum;
/**
 * Enum for provider types.
 *
 * @since 0.1.0
 *
 * @method static self cloud() Creates an instance for CLOUD type.
 * @method static self server() Creates an instance for SERVER type.
 * @method static self client() Creates an instance for CLIENT type.
 * @method bool isCloud() Checks if the type is CLOUD.
 * @method bool isServer() Checks if the type is SERVER.
 * @method bool isClient() Checks if the type is CLIENT.
 */
class ProviderTypeEnum extends AbstractEnum
{
    /**
     * Cloud-based AI provider (e.g. models available via external REST APIs).
     */
    public const CLOUD = 'cloud';
    /**
     * Server-side AI provider (e.g. self-hosted models).
     */
    public const SERVER = 'server';
    /**
     * Client-side AI provider (e.g. browser-based models).
     */
    public const CLIENT = 'client';
}
PK�e]ד�5�� Providers/Enums/ToolTypeEnum.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Enums;

use WordPress\AiClient\Common\AbstractEnum;
/**
 * Enum for tool types.
 *
 * @since 0.1.0
 *
 * @method static self functionDeclarations() Creates an instance for FUNCTION_DECLARATIONS type.
 * @method static self webSearch() Creates an instance for WEB_SEARCH type.
 * @method bool isFunctionDeclarations() Checks if the type is FUNCTION_DECLARATIONS.
 * @method bool isWebSearch() Checks if the type is WEB_SEARCH.
 */
class ToolTypeEnum extends AbstractEnum
{
    /**
     * Function declarations tool type.
     */
    public const FUNCTION_DECLARATIONS = 'function_declarations';
    /**
     * Web search tool type.
     */
    public const WEB_SEARCH = 'web_search';
}
PK�e]����b�bXProviders/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\OpenAiCompatibleImplementation;

use WordPress\AiClient\Common\Exception\InvalidArgumentException;
use WordPress\AiClient\Common\Exception\RuntimeException;
use WordPress\AiClient\Messages\DTO\Message;
use WordPress\AiClient\Messages\DTO\MessagePart;
use WordPress\AiClient\Messages\Enums\MessagePartChannelEnum;
use WordPress\AiClient\Messages\Enums\MessageRoleEnum;
use WordPress\AiClient\Messages\Enums\ModalityEnum;
use WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel;
use WordPress\AiClient\Providers\Http\DTO\Request;
use WordPress\AiClient\Providers\Http\DTO\Response;
use WordPress\AiClient\Providers\Http\Enums\HttpMethodEnum;
use WordPress\AiClient\Providers\Http\Exception\ResponseException;
use WordPress\AiClient\Providers\Http\Util\ResponseUtil;
use WordPress\AiClient\Providers\Models\TextGeneration\Contracts\TextGenerationModelInterface;
use WordPress\AiClient\Results\DTO\Candidate;
use WordPress\AiClient\Results\DTO\GenerativeAiResult;
use WordPress\AiClient\Results\DTO\TokenUsage;
use WordPress\AiClient\Results\Enums\FinishReasonEnum;
use WordPress\AiClient\Tools\DTO\FunctionCall;
use WordPress\AiClient\Tools\DTO\FunctionDeclaration;
/**
 * Base class for a text generation model for providers that implement OpenAI's API format.
 *
 * This abstract class is designed to work with any AI provider that offers an OpenAI-compatible
 * API endpoint, including but not limited to Anthropic, Google, and other providers
 * that have adopted OpenAI's API specification as a standard interface.
 *
 * @since 0.1.0
 *
 * @phpstan-type ToolCallData array{
 *     type?: string,
 *     id?: string,
 *     function?: array{
 *         name?: string,
 *         arguments: string|array<string, mixed>
 *     }
 * }
 * @phpstan-type MessageData array{
 *     role?: string,
 *     reasoning_content?: string,
 *     content?: string,
 *     tool_calls?: list<ToolCallData>
 * }
 * @phpstan-type ChoiceData array{
 *     message?: MessageData,
 *     finish_reason?: string
 * }
 * @phpstan-type UsageData array{
 *     prompt_tokens?: int,
 *     completion_tokens?: int,
 *     total_tokens?: int
 * }
 * @phpstan-type ResponseData array{
 *     id?: string,
 *     choices?: list<ChoiceData>,
 *     usage?: UsageData
 * }
 */
abstract class AbstractOpenAiCompatibleTextGenerationModel extends AbstractApiBasedModel implements TextGenerationModelInterface
{
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    final public function generateTextResult(array $prompt): GenerativeAiResult
    {
        $httpTransporter = $this->getHttpTransporter();
        $params = $this->prepareGenerateTextParams($prompt);
        $request = $this->createRequest(HttpMethodEnum::POST(), 'chat/completions', ['Content-Type' => 'application/json'], $params);
        // Add authentication credentials to the request.
        $request = $this->getRequestAuthentication()->authenticateRequest($request);
        // Send and process the request.
        $response = $httpTransporter->send($request);
        $this->throwIfNotSuccessful($response);
        return $this->parseResponseToGenerativeAiResult($response);
    }
    /**
     * Prepares the given prompt and the model configuration into parameters for the API request.
     *
     * @since 0.1.0
     *
     * @param list<Message> $prompt The prompt to generate text for. Either a single message or a list of messages
     *                              from a chat.
     * @return array<string, mixed> The parameters for the API request.
     */
    protected function prepareGenerateTextParams(array $prompt): array
    {
        $config = $this->getConfig();
        $params = ['model' => $this->metadata()->getId(), 'messages' => $this->prepareMessagesParam($prompt, $config->getSystemInstruction())];
        $outputModalities = $config->getOutputModalities();
        if (is_array($outputModalities)) {
            $this->validateOutputModalities($outputModalities);
            if (count($outputModalities) > 1) {
                $params['modalities'] = $this->prepareOutputModalitiesParam($outputModalities);
            }
        }
        $candidateCount = $config->getCandidateCount();
        if ($candidateCount !== null) {
            $params['n'] = $candidateCount;
        }
        $maxTokens = $config->getMaxTokens();
        if ($maxTokens !== null) {
            $params['max_tokens'] = $maxTokens;
        }
        $temperature = $config->getTemperature();
        if ($temperature !== null) {
            $params['temperature'] = $temperature;
        }
        $topP = $config->getTopP();
        if ($topP !== null) {
            $params['top_p'] = $topP;
        }
        $stopSequences = $config->getStopSequences();
        if (is_array($stopSequences)) {
            $params['stop'] = $stopSequences;
        }
        $presencePenalty = $config->getPresencePenalty();
        if ($presencePenalty !== null) {
            $params['presence_penalty'] = $presencePenalty;
        }
        $frequencyPenalty = $config->getFrequencyPenalty();
        if ($frequencyPenalty !== null) {
            $params['frequency_penalty'] = $frequencyPenalty;
        }
        $logprobs = $config->getLogprobs();
        if ($logprobs !== null) {
            $params['logprobs'] = $logprobs;
        }
        $topLogprobs = $config->getTopLogprobs();
        if ($topLogprobs !== null) {
            $params['top_logprobs'] = $topLogprobs;
        }
        $functionDeclarations = $config->getFunctionDeclarations();
        if (is_array($functionDeclarations)) {
            $params['tools'] = $this->prepareToolsParam($functionDeclarations);
        }
        $outputMimeType = $config->getOutputMimeType();
        if ('application/json' === $outputMimeType) {
            $outputSchema = $config->getOutputSchema();
            $params['response_format'] = $this->prepareResponseFormatParam($outputSchema);
        }
        /*
         * Any custom options are added to the parameters as well.
         * This allows developers to pass other options that may be more niche or not yet supported by the SDK.
         */
        $customOptions = $config->getCustomOptions();
        foreach ($customOptions as $key => $value) {
            if (isset($params[$key])) {
                throw new InvalidArgumentException(sprintf('The custom option "%s" conflicts with an existing parameter.', $key));
            }
            $params[$key] = $value;
        }
        return $params;
    }
    /**
     * Prepares the messages parameter for the API request.
     *
     * @since 0.1.0
     *
     * @param list<Message> $messages The messages to prepare.
     * @param string|null $systemInstruction An optional system instruction to prepend to the messages.
     * @return list<array<string, mixed>> The prepared messages parameter.
     */
    protected function prepareMessagesParam(array $messages, ?string $systemInstruction = null): array
    {
        $messagesParam = array_map(function (Message $message): array {
            // Special case: Function response.
            $messageParts = $message->getParts();
            if (count($messageParts) === 1 && $messageParts[0]->getType()->isFunctionResponse()) {
                $functionResponse = $messageParts[0]->getFunctionResponse();
                if (!$functionResponse) {
                    // This should be impossible due to class internals, but still needs to be checked.
                    throw new RuntimeException('The function response typed message part must contain a function response.');
                }
                return ['role' => 'tool', 'content' => json_encode($functionResponse->getResponse()), 'tool_call_id' => $functionResponse->getId()];
            }
            $messageData = ['role' => $this->getMessageRoleString($message->getRole()), 'content' => array_values(array_filter(array_map([$this, 'getMessagePartContentData'], $messageParts)))];
            // Only include tool_calls if there are any (OpenAI rejects empty arrays).
            $toolCalls = array_values(array_filter(array_map([$this, 'getMessagePartToolCallData'], $messageParts)));
            if (!empty($toolCalls)) {
                $messageData['tool_calls'] = $toolCalls;
            }
            return $messageData;
        }, $messages);
        if ($systemInstruction) {
            array_unshift($messagesParam, [
                /*
                 * TODO: Replace this with 'developer' in the future.
                 * See https://platform.openai.com/docs/api-reference/chat/create#chat_create-messages
                 */
                'role' => 'system',
                'content' => [['type' => 'text', 'text' => $systemInstruction]],
            ]);
        }
        return $messagesParam;
    }
    /**
     * Returns the OpenAI API specific role string for the given message role.
     *
     * @since 0.1.0
     *
     * @param MessageRoleEnum $role The message role.
     * @return string The role for the API request.
     */
    protected function getMessageRoleString(MessageRoleEnum $role): string
    {
        if ($role === MessageRoleEnum::model()) {
            return 'assistant';
        }
        return 'user';
    }
    /**
     * Returns the OpenAI API specific content data for a message part.
     *
     * @since 0.1.0
     *
     * @param MessagePart $part The message part to get the data for.
     * @return ?array<string, mixed> The data for the message content part, or null if not applicable.
     * @throws InvalidArgumentException If the message part type or data is unsupported.
     */
    protected function getMessagePartContentData(MessagePart $part): ?array
    {
        $type = $part->getType();
        if ($type->isText()) {
            /*
             * The OpenAI Chat Completions API spec does not support annotating thought parts as input,
             * so we instead skip them.
             */
            if ($part->getChannel()->isThought()) {
                return null;
            }
            return ['type' => 'text', 'text' => $part->getText()];
        }
        if ($type->isFile()) {
            $file = $part->getFile();
            if (!$file) {
                // This should be impossible due to class internals, but still needs to be checked.
                throw new RuntimeException('The file typed message part must contain a file.');
            }
            if ($file->isRemote()) {
                if ($file->isImage()) {
                    return ['type' => 'image_url', 'image_url' => ['url' => $file->getUrl()]];
                }
                throw new InvalidArgumentException(sprintf('Unsupported MIME type "%s" for remote file message part.', $file->getMimeType()));
            }
            // Else, it is an inline file.
            if ($file->isImage()) {
                return ['type' => 'image_url', 'image_url' => ['url' => $file->getDataUri()]];
            }
            if ($file->isAudio()) {
                return ['type' => 'input_audio', 'input_audio' => ['data' => $file->getBase64Data(), 'format' => $file->getMimeTypeObject()->toExtension()]];
            }
            throw new InvalidArgumentException(sprintf('Unsupported MIME type "%s" for inline file message part.', $file->getMimeType()));
        }
        if ($type->isFunctionCall()) {
            // Skip, as this is separately included. See `getMessagePartToolCallData()`.
            return null;
        }
        if ($type->isFunctionResponse()) {
            // Special case: Function response.
            throw new InvalidArgumentException('The API only allows a single function response, as the only content of the message.');
        }
        throw new InvalidArgumentException(sprintf('Unsupported message part type "%s".', $type));
    }
    /**
     * Returns the OpenAI API specific tool calls data for a message part.
     *
     * @since 0.1.0
     *
     * @param MessagePart $part The message part to get the data for.
     * @return ?array<string, mixed> The data for the message tool call part, or null if not applicable.
     * @throws InvalidArgumentException If the message part type or data is unsupported.
     */
    protected function getMessagePartToolCallData(MessagePart $part): ?array
    {
        $type = $part->getType();
        if ($type->isFunctionCall()) {
            $functionCall = $part->getFunctionCall();
            if (!$functionCall) {
                // This should be impossible due to class internals, but still needs to be checked.
                throw new RuntimeException('The function call typed message part must contain a function call.');
            }
            $args = $functionCall->getArgs();
            /*
             * Ensure null or empty arrays become empty objects for JSON encoding.
             * While in theory the JSON schema could also dictate a type of
             * 'array', in practice function arguments are typically of type
             * 'object'. More importantly, the OpenAI API specification seems
             * to expect that, and does not support passing arrays as the root
             * value. The null check handles the case where FunctionCall normalizes
             * empty arrays to null.
             */
            if ($args === null || is_array($args) && count($args) === 0) {
                $args = new \stdClass();
            }
            return ['type' => 'function', 'id' => $functionCall->getId(), 'function' => ['name' => $functionCall->getName(), 'arguments' => json_encode($args)]];
        }
        // All other types are handled in `getMessagePartContentData()`.
        return null;
    }
    /**
     * Validates that the given output modalities to ensure that at least one output modality is text.
     *
     * @since 0.1.0
     *
     * @param array<ModalityEnum> $outputModalities The output modalities to validate.
     * @throws InvalidArgumentException If no text output modality is present.
     */
    protected function validateOutputModalities(array $outputModalities): void
    {
        // If no output modalities are set, it's fine, as we can assume text.
        if (count($outputModalities) === 0) {
            return;
        }
        foreach ($outputModalities as $modality) {
            if ($modality->isText()) {
                return;
            }
        }
        throw new InvalidArgumentException('A text output modality must be present when generating text.');
    }
    /**
     * Prepares the output modalities parameter for the API request.
     *
     * @since 0.1.0
     *
     * @param array<ModalityEnum> $modalities The modalities to prepare.
     * @return list<string> The prepared modalities parameter.
     */
    protected function prepareOutputModalitiesParam(array $modalities): array
    {
        $prepared = [];
        foreach ($modalities as $modality) {
            if ($modality->isText()) {
                $prepared[] = 'text';
            } elseif ($modality->isImage()) {
                $prepared[] = 'image';
            } elseif ($modality->isAudio()) {
                $prepared[] = 'audio';
            } else {
                throw new InvalidArgumentException(sprintf('Unsupported output modality "%s".', $modality));
            }
        }
        return $prepared;
    }
    /**
     * Prepares the tools parameter for the API request.
     *
     * @since 0.1.0
     *
     * @param list<FunctionDeclaration> $functionDeclarations The function declarations.
     * @return list<array<string, mixed>> The prepared tools parameter.
     */
    protected function prepareToolsParam(array $functionDeclarations): array
    {
        $tools = [];
        foreach ($functionDeclarations as $functionDeclaration) {
            $tools[] = ['type' => 'function', 'function' => $functionDeclaration->toArray()];
        }
        return $tools;
    }
    /**
     * Prepares the response format parameter for the API request.
     *
     * This is only called if the output MIME type is `application/json`.
     *
     * @since 0.1.0
     *
     * @param array<string, mixed>|null $outputSchema The output schema.
     * @return array<string, mixed> The prepared response format parameter.
     */
    protected function prepareResponseFormatParam(?array $outputSchema): array
    {
        if (is_array($outputSchema)) {
            return ['type' => 'json_schema', 'json_schema' => $outputSchema];
        }
        return ['type' => 'json_object'];
    }
    /**
     * Creates a request object for the provider's API.
     *
     * Implementations should use $this->getRequestOptions() to attach any
     * configured request options to the Request.
     *
     * @since 0.1.0
     *
     * @param HttpMethodEnum $method The HTTP method.
     * @param string $path The API endpoint path, relative to the base URI.
     * @param array<string, string|list<string>> $headers The request headers.
     * @param string|array<string, mixed>|null $data The request data.
     * @return Request The request object.
     */
    abstract protected function createRequest(HttpMethodEnum $method, string $path, array $headers = [], $data = null): Request;
    /**
     * Throws an exception if the response is not successful.
     *
     * @since 0.1.0
     *
     * @param Response $response The HTTP response to check.
     * @throws ResponseException If the response is not successful.
     */
    protected function throwIfNotSuccessful(Response $response): void
    {
        /*
         * While this method only calls the utility method, it's important to have it here as a protected method so
         * that child classes can override it if needed.
         */
        ResponseUtil::throwIfNotSuccessful($response);
    }
    /**
     * Parses the response from the API endpoint to a generative AI result.
     *
     * @since 0.1.0
     *
     * @param Response $response The response from the API endpoint.
     * @return GenerativeAiResult The parsed generative AI result.
     */
    protected function parseResponseToGenerativeAiResult(Response $response): GenerativeAiResult
    {
        /** @var ResponseData $responseData */
        $responseData = $response->getData();
        if (!isset($responseData['choices']) || !$responseData['choices']) {
            throw ResponseException::fromMissingData($this->providerMetadata()->getName(), 'choices');
        }
        if (!is_array($responseData['choices'])) {
            throw ResponseException::fromInvalidData($this->providerMetadata()->getName(), 'choices', 'The value must be an array.');
        }
        $candidates = [];
        foreach ($responseData['choices'] as $index => $choiceData) {
            if (!is_array($choiceData) || array_is_list($choiceData)) {
                throw ResponseException::fromInvalidData($this->providerMetadata()->getName(), "choices[{$index}]", 'The value must be an associative array.');
            }
            $candidates[] = $this->parseResponseChoiceToCandidate($choiceData, $index);
        }
        $id = isset($responseData['id']) && is_string($responseData['id']) ? $responseData['id'] : '';
        if (isset($responseData['usage']) && is_array($responseData['usage'])) {
            $usage = $responseData['usage'];
            $tokenUsage = new TokenUsage($usage['prompt_tokens'] ?? 0, $usage['completion_tokens'] ?? 0, $usage['total_tokens'] ?? 0);
        } else {
            $tokenUsage = new TokenUsage(0, 0, 0);
        }
        // Use any other data from the response as provider-specific response metadata.
        $additionalData = $responseData;
        unset($additionalData['id'], $additionalData['choices'], $additionalData['usage']);
        return new GenerativeAiResult($id, $candidates, $tokenUsage, $this->providerMetadata(), $this->metadata(), $additionalData);
    }
    /**
     * Parses a single choice from the API response into a Candidate object.
     *
     * @since 0.1.0
     *
     * @param ChoiceData $choiceData The choice data from the API response.
     * @param int $index The index of the choice in the choices array.
     * @return Candidate The parsed candidate.
     * @throws RuntimeException If the choice data is invalid.
     */
    protected function parseResponseChoiceToCandidate(array $choiceData, int $index): Candidate
    {
        if (!isset($choiceData['message']) || !is_array($choiceData['message']) || array_is_list($choiceData['message'])) {
            throw ResponseException::fromMissingData($this->providerMetadata()->getName(), "choices[{$index}].message");
        }
        if (!isset($choiceData['finish_reason']) || !is_string($choiceData['finish_reason'])) {
            throw ResponseException::fromMissingData($this->providerMetadata()->getName(), "choices[{$index}].finish_reason");
        }
        $messageData = $choiceData['message'];
        $message = $this->parseResponseChoiceMessage($messageData, $index);
        switch ($choiceData['finish_reason']) {
            case 'stop':
                $finishReason = FinishReasonEnum::stop();
                break;
            case 'length':
                $finishReason = FinishReasonEnum::length();
                break;
            case 'content_filter':
                $finishReason = FinishReasonEnum::contentFilter();
                break;
            case 'tool_calls':
                $finishReason = FinishReasonEnum::toolCalls();
                break;
            default:
                throw ResponseException::fromInvalidData($this->providerMetadata()->getName(), "choices[{$index}].finish_reason", sprintf('Invalid finish reason "%s".', $choiceData['finish_reason']));
        }
        return new Candidate($message, $finishReason);
    }
    /**
     * Parses the message from a choice in the API response.
     *
     * @since 0.1.0
     *
     * @param MessageData $messageData The message data from the API response.
     * @param int $index The index of the choice in the choices array.
     * @return Message The parsed message.
     */
    protected function parseResponseChoiceMessage(array $messageData, int $index): Message
    {
        $role = isset($messageData['role']) && 'user' === $messageData['role'] ? MessageRoleEnum::user() : MessageRoleEnum::model();
        $parts = $this->parseResponseChoiceMessageParts($messageData, $index);
        return new Message($role, $parts);
    }
    /**
     * Parses the message parts from a choice in the API response.
     *
     * @since 0.1.0
     *
     * @param MessageData $messageData The message data from the API response.
     * @param int $index The index of the choice in the choices array.
     * @return MessagePart[] The parsed message parts.
     */
    protected function parseResponseChoiceMessageParts(array $messageData, int $index): array
    {
        $parts = [];
        if (isset($messageData['reasoning_content']) && is_string($messageData['reasoning_content'])) {
            $parts[] = new MessagePart($messageData['reasoning_content'], MessagePartChannelEnum::thought());
        }
        if (isset($messageData['content']) && is_string($messageData['content'])) {
            $parts[] = new MessagePart($messageData['content']);
        }
        if (isset($messageData['tool_calls']) && is_array($messageData['tool_calls'])) {
            foreach ($messageData['tool_calls'] as $toolCallIndex => $toolCallData) {
                $toolCallPart = $this->parseResponseChoiceMessageToolCallPart($toolCallData);
                if (!$toolCallPart) {
                    throw ResponseException::fromInvalidData($this->providerMetadata()->getName(), "choices[{$index}].message.tool_calls[{$toolCallIndex}]", 'The response includes a tool call of an unexpected type.');
                }
                $parts[] = $toolCallPart;
            }
        }
        return $parts;
    }
    /**
     * Parses a tool call part from the API response.
     *
     * @since 0.1.0
     *
     * @param ToolCallData $toolCallData The tool call data from the API response.
     * @return MessagePart|null The parsed message part for the tool call, or null if not applicable.
     */
    protected function parseResponseChoiceMessageToolCallPart(array $toolCallData): ?MessagePart
    {
        /*
         * For now, only function calls are supported.
         *
         * Not all OpenAI compatible APIs include a 'type' key, so we only check its value if it is set.
         */
        if (isset($toolCallData['type']) && 'function' !== $toolCallData['type'] || !isset($toolCallData['function']) || !is_array($toolCallData['function'])) {
            return null;
        }
        $functionArguments = is_string($toolCallData['function']['arguments']) ? json_decode($toolCallData['function']['arguments'], \true) : $toolCallData['function']['arguments'];
        $functionCall = new FunctionCall(isset($toolCallData['id']) && is_string($toolCallData['id']) ? $toolCallData['id'] : null, isset($toolCallData['function']['name']) && is_string($toolCallData['function']['name']) ? $toolCallData['function']['name'] : null, $functionArguments);
        return new MessagePart($functionCall);
    }
}
PK�e]��F��3�3YProviders/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\OpenAiCompatibleImplementation;

use WordPress\AiClient\Common\Exception\InvalidArgumentException;
use WordPress\AiClient\Common\Exception\RuntimeException;
use WordPress\AiClient\Files\DTO\File;
use WordPress\AiClient\Files\Enums\MediaOrientationEnum;
use WordPress\AiClient\Messages\DTO\Message;
use WordPress\AiClient\Messages\DTO\MessagePart;
use WordPress\AiClient\Messages\Enums\MessageRoleEnum;
use WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel;
use WordPress\AiClient\Providers\Http\DTO\Request;
use WordPress\AiClient\Providers\Http\DTO\Response;
use WordPress\AiClient\Providers\Http\Enums\HttpMethodEnum;
use WordPress\AiClient\Providers\Http\Exception\ResponseException;
use WordPress\AiClient\Providers\Http\Util\ResponseUtil;
use WordPress\AiClient\Providers\Models\ImageGeneration\Contracts\ImageGenerationModelInterface;
use WordPress\AiClient\Results\DTO\Candidate;
use WordPress\AiClient\Results\DTO\GenerativeAiResult;
use WordPress\AiClient\Results\DTO\TokenUsage;
use WordPress\AiClient\Results\Enums\FinishReasonEnum;
/**
 * Base class for an image generation model for providers that implement OpenAI's API format.
 *
 * This abstract class is designed to work with any AI provider that offers an OpenAI-compatible
 * API endpoint for image generation, including but not limited to Anthropic, Google, and other
 * providers that have adopted OpenAI's image generation API specification as a standard interface.
 *
 * @since 0.1.0
 *
 * @phpstan-type ImageGenerationParams array{
 *     model: string,
 *     prompt: string,
 *     n?: int,
 *     response_format?: string,
 *     output_format?: string|null,
 *     size?: string,
 *     ...
 * }
 * @phpstan-type ChoiceData array{
 *     url?: string,
 *     b64_json?: string
 * }
 * @phpstan-type UsageData array{
 *     input_tokens?: int,
 *     output_tokens?: int,
 *     total_tokens?: int
 * }
 * @phpstan-type ResponseData array{
 *     id?: string,
 *     data?: list<ChoiceData>,
 *     usage?: UsageData
 * }
 */
abstract class AbstractOpenAiCompatibleImageGenerationModel extends AbstractApiBasedModel implements ImageGenerationModelInterface
{
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public function generateImageResult(array $prompt): GenerativeAiResult
    {
        $httpTransporter = $this->getHttpTransporter();
        $params = $this->prepareGenerateImageParams($prompt);
        $request = $this->createRequest(HttpMethodEnum::POST(), 'images/generations', ['Content-Type' => 'application/json'], $params);
        // Add authentication credentials to the request.
        $request = $this->getRequestAuthentication()->authenticateRequest($request);
        // Send and process the request.
        $response = $httpTransporter->send($request);
        $this->throwIfNotSuccessful($response);
        return $this->parseResponseToGenerativeAiResult($response, isset($params['output_format']) && is_string($params['output_format']) ? "image/{$params['output_format']}" : 'image/png');
    }
    /**
     * Prepares the given prompt and the model configuration into parameters for the API request.
     *
     * @since 0.1.0
     *
     * @param list<Message> $prompt The prompt to generate an image for. Either a single message or a list of messages
     *                              from a chat. However as of today, OpenAI compatible image generation endpoints only
     *                              support a single user message.
     * @return ImageGenerationParams The parameters for the API request.
     */
    protected function prepareGenerateImageParams(array $prompt): array
    {
        $config = $this->getConfig();
        $params = ['model' => $this->metadata()->getId(), 'prompt' => $this->preparePromptParam($prompt)];
        $candidateCount = $config->getCandidateCount();
        if ($candidateCount !== null) {
            $params['n'] = $candidateCount;
        }
        $outputFileType = $config->getOutputFileType();
        if ($outputFileType !== null) {
            $params['response_format'] = $outputFileType->isRemote() ? 'url' : 'b64_json';
        } else {
            // The 'response_format' parameter is required, so we default to 'b64_json' if not set.
            $params['response_format'] = 'b64_json';
        }
        $outputMimeType = $config->getOutputMimeType();
        if ($outputMimeType !== null) {
            $params['output_format'] = preg_replace('/^image\//', '', $outputMimeType);
        }
        $outputMediaOrientation = $config->getOutputMediaOrientation();
        $outputMediaAspectRatio = $config->getOutputMediaAspectRatio();
        if ($outputMediaOrientation !== null || $outputMediaAspectRatio !== null) {
            $params['size'] = $this->prepareSizeParam($outputMediaOrientation, $outputMediaAspectRatio);
        }
        /*
         * Any custom options are added to the parameters as well.
         * This allows developers to pass other options that may be more niche or not yet supported by the SDK.
         */
        $customOptions = $config->getCustomOptions();
        foreach ($customOptions as $key => $value) {
            if (isset($params[$key])) {
                throw new InvalidArgumentException(sprintf('The custom option "%s" conflicts with an existing parameter.', $key));
            }
            $params[$key] = $value;
        }
        /** @var ImageGenerationParams $params */
        return $params;
    }
    /**
     * Prepares the prompt parameter for the API request.
     *
     * @since 0.1.0
     *
     * @param list<Message> $messages The messages to prepare. However as of today, OpenAI compatible image generation
     *                                endpoints only support a single user message.
     * @return string The prepared prompt parameter.
     */
    protected function preparePromptParam(array $messages): string
    {
        if (count($messages) !== 1) {
            throw new InvalidArgumentException('The API requires a single user message as prompt.');
        }
        $message = $messages[0];
        if (!$message->getRole()->isUser()) {
            throw new InvalidArgumentException('The API requires a user message as prompt.');
        }
        $text = null;
        foreach ($message->getParts() as $part) {
            $text = $part->getText();
            if ($text !== null) {
                break;
            }
        }
        if ($text === null) {
            throw new InvalidArgumentException('The API requires a single text message part as prompt.');
        }
        return $text;
    }
    /**
     * Prepares the size parameter for the API request.
     *
     * @since 0.1.0
     *
     * @param MediaOrientationEnum|null $orientation The desired media orientation.
     * @param string|null $aspectRatio The desired media aspect ratio.
     * @return string The prepared size parameter.
     */
    protected function prepareSizeParam(?MediaOrientationEnum $orientation, ?string $aspectRatio): string
    {
        // Use aspect ratio if set, as it is more specific.
        if ($aspectRatio !== null) {
            switch ($aspectRatio) {
                case '1:1':
                    return '1024x1024';
                case '3:2':
                    return '1536x1024';
                case '7:4':
                    return '1792x1024';
                case '2:3':
                    return '1024x1536';
                case '4:7':
                    return '1024x1792';
                default:
                    throw new InvalidArgumentException('The aspect ratio "' . $aspectRatio . '" is not supported.');
            }
        }
        // This should always have a value, as the method is only called if at least one or the other is set.
        if ($orientation !== null) {
            if ($orientation->isLandscape()) {
                return '1536x1024';
            }
            if ($orientation->isPortrait()) {
                return '1024x1536';
            }
        }
        return '1024x1024';
    }
    /**
     * Creates a request object for the provider's API.
     *
     * Implementations should use $this->getRequestOptions() to attach any
     * configured request options to the Request.
     *
     * @since 0.1.0
     *
     * @param HttpMethodEnum $method The HTTP method.
     * @param string $path The API endpoint path, relative to the base URI.
     * @param array<string, string|list<string>> $headers The request headers.
     * @param string|array<string, mixed>|null $data The request data.
     * @return Request The request object.
     */
    abstract protected function createRequest(HttpMethodEnum $method, string $path, array $headers = [], $data = null): Request;
    /**
     * Throws an exception if the response is not successful.
     *
     * @since 0.1.0
     *
     * @param Response $response The HTTP response to check.
     * @throws ResponseException If the response is not successful.
     */
    protected function throwIfNotSuccessful(Response $response): void
    {
        /*
         * While this method only calls the utility method, it's important to have it here as a protected method so
         * that child classes can override it if needed.
         */
        ResponseUtil::throwIfNotSuccessful($response);
    }
    /**
     * Parses the response from the API endpoint to a generative AI result.
     *
     * @since 0.1.0
     *
     * @param Response $response The response from the API endpoint.
     * @param string   $expectedMimeType The expected MIME type the response is in.
     * @return GenerativeAiResult The parsed generative AI result.
     */
    protected function parseResponseToGenerativeAiResult(Response $response, string $expectedMimeType = 'image/png'): GenerativeAiResult
    {
        /** @var ResponseData $responseData */
        $responseData = $response->getData();
        if (!isset($responseData['data']) || !$responseData['data']) {
            throw ResponseException::fromMissingData($this->providerMetadata()->getName(), 'data');
        }
        if (!is_array($responseData['data'])) {
            throw ResponseException::fromInvalidData($this->providerMetadata()->getName(), 'data', 'The value must be an array.');
        }
        $candidates = [];
        foreach ($responseData['data'] as $index => $choiceData) {
            if (!is_array($choiceData) || array_is_list($choiceData)) {
                throw ResponseException::fromInvalidData($this->providerMetadata()->getName(), "data[{$index}]", 'The value must be an associative array.');
            }
            $candidates[] = $this->parseResponseChoiceToCandidate($choiceData, $index, $expectedMimeType);
        }
        $id = $this->getResultId($responseData);
        if (isset($responseData['usage']) && is_array($responseData['usage'])) {
            $usage = $responseData['usage'];
            $tokenUsage = new TokenUsage($usage['input_tokens'] ?? 0, $usage['output_tokens'] ?? 0, $usage['total_tokens'] ?? 0);
        } else {
            $tokenUsage = new TokenUsage(0, 0, 0);
        }
        // Use any other data from the response as provider-specific response metadata.
        $providerMetadata = $responseData;
        unset($providerMetadata['id'], $providerMetadata['data'], $providerMetadata['usage']);
        return new GenerativeAiResult($id, $candidates, $tokenUsage, $this->providerMetadata(), $this->metadata(), $providerMetadata);
    }
    /**
     * Parses a single choice from the API response into a Candidate object.
     *
     * @since 0.1.0
     *
     * @param ChoiceData $choiceData The choice data from the API response.
     * @param int $index The index of the choice in the choices array.
     * @param string   $expectedMimeType The expected MIME type the response is in.
     * @return Candidate The parsed candidate.
     * @throws RuntimeException If the choice data is invalid.
     */
    protected function parseResponseChoiceToCandidate(array $choiceData, int $index, string $expectedMimeType = 'image/png'): Candidate
    {
        if (isset($choiceData['url']) && is_string($choiceData['url'])) {
            $imageFile = new File($choiceData['url'], $expectedMimeType);
        } elseif (isset($choiceData['b64_json']) && is_string($choiceData['b64_json'])) {
            $imageFile = new File($choiceData['b64_json'], $expectedMimeType);
        } else {
            throw ResponseException::fromInvalidData($this->providerMetadata()->getName(), "choices[{$index}]", 'The value must contain either a url or b64_json key with a string value.');
        }
        $parts = [new MessagePart($imageFile)];
        $message = new Message(MessageRoleEnum::model(), $parts);
        return new Candidate($message, FinishReasonEnum::stop());
    }
    /**
     * Extracts the result ID from the API response data.
     *
     * @since 0.4.0
     *
     * @param array<string, mixed> $responseData The response data from the API.
     * @return string The result ID.
     */
    protected function getResultId(array $responseData): string
    {
        return isset($responseData['id']) && is_string($responseData['id']) ? $responseData['id'] : '';
    }
}
PK�e]�gä��[Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\OpenAiCompatibleImplementation;

use WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory;
use WordPress\AiClient\Providers\Http\DTO\Request;
use WordPress\AiClient\Providers\Http\DTO\Response;
use WordPress\AiClient\Providers\Http\Enums\HttpMethodEnum;
use WordPress\AiClient\Providers\Http\Exception\ResponseException;
use WordPress\AiClient\Providers\Http\Util\ResponseUtil;
use WordPress\AiClient\Providers\Models\DTO\ModelMetadata;
/**
 * Base class for a model metadata directory for providers that implement OpenAI's API format.
 *
 * This abstract class is designed to work with any AI provider that offers an OpenAI-compatible
 * models listing endpoint, including but not limited to Anthropic, Google, and other
 * providers that have adopted OpenAI's models API specification as a standard interface.
 *
 * @since 0.1.0
 */
abstract class AbstractOpenAiCompatibleModelMetadataDirectory extends AbstractApiBasedModelMetadataDirectory
{
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    protected function sendListModelsRequest(): array
    {
        $httpTransporter = $this->getHttpTransporter();
        $request = $this->createRequest(HttpMethodEnum::GET(), 'models');
        $request = $this->getRequestAuthentication()->authenticateRequest($request);
        $response = $httpTransporter->send($request);
        $this->throwIfNotSuccessful($response);
        $modelsMetadataList = $this->parseResponseToModelMetadataList($response);
        $modelMetadataMap = [];
        foreach ($modelsMetadataList as $modelMetadata) {
            $modelMetadataMap[$modelMetadata->getId()] = $modelMetadata;
        }
        return $modelMetadataMap;
    }
    /**
     * Creates a request object for the provider's API.
     *
     * @since 0.1.0
     *
     * @param HttpMethodEnum $method The HTTP method.
     * @param string $path The API endpoint path, relative to the base URI.
     * @param array<string, string|list<string>> $headers The request headers.
     * @param string|array<string, mixed>|null $data The request data.
     * @return Request The request object.
     */
    abstract protected function createRequest(HttpMethodEnum $method, string $path, array $headers = [], $data = null): Request;
    /**
     * Throws an exception if the response is not successful.
     *
     * @since 0.1.0
     *
     * @param Response $response The HTTP response to check.
     * @throws ResponseException If the response is not successful.
     */
    protected function throwIfNotSuccessful(Response $response): void
    {
        /*
         * While this method only calls the utility method, it's important to have it here as a protected method so
         * that child classes can override it if needed.
         */
        ResponseUtil::throwIfNotSuccessful($response);
    }
    /**
     * Parses the response from the API endpoint to list models into a list of model metadata objects.
     *
     * @since 0.1.0
     *
     * @param Response $response The response from the API endpoint to list models.
     * @return list<ModelMetadata> List of model metadata objects.
     */
    abstract protected function parseResponseToModelMetadataList(Response $response): array;
}
PK�e]�Q���>Providers/Contracts/ProviderWithOperationsHandlerInterface.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Contracts;

/**
 * Interface for providers that support operations handlers.
 *
 * Providers implementing this interface can return an operations handler
 * for managing long-running operations across all their models.
 *
 * @since 0.1.0
 */
interface ProviderWithOperationsHandlerInterface
{
    /**
     * Gets the operations handler for this provider.
     *
     * @since 0.1.0
     *
     * @return ProviderOperationsHandlerInterface The operations handler.
     */
    public static function operationsHandler(): \WordPress\AiClient\Providers\Contracts\ProviderOperationsHandlerInterface;
}
PK�e]Xi��..5Providers/Contracts/ProviderAvailabilityInterface.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Contracts;

/**
 * Interface for checking provider availability.
 *
 * Determines whether a provider is configured and available
 * for use based on API keys, credentials, or other requirements.
 *
 * @since 0.1.0
 */
interface ProviderAvailabilityInterface
{
    /**
     * Checks if the provider is configured.
     *
     * @since 0.1.0
     *
     * @return bool True if the provider is configured and available, false otherwise.
     */
    public function isConfigured(): bool;
}
PK�e]0�S���)Providers/Contracts/ProviderInterface.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Contracts;

use WordPress\AiClient\Common\Exception\InvalidArgumentException;
use WordPress\AiClient\Providers\DTO\ProviderMetadata;
use WordPress\AiClient\Providers\Models\Contracts\ModelInterface;
use WordPress\AiClient\Providers\Models\DTO\ModelConfig;
/**
 * Interface for AI providers.
 *
 * Providers represent AI services (Google, OpenAI, Anthropic, etc.)
 * and provide access to models, metadata, and availability information.
 *
 * @since 0.1.0
 */
interface ProviderInterface
{
    /**
     * Gets provider metadata.
     *
     * @since 0.1.0
     *
     * @return ProviderMetadata Provider metadata.
     */
    public static function metadata(): ProviderMetadata;
    /**
     * Creates a model instance.
     *
     * @since 0.1.0
     *
     * @param string $modelId Model identifier.
     * @param ?ModelConfig $modelConfig Model configuration.
     * @return ModelInterface Model instance.
     * @throws InvalidArgumentException If model not found or configuration invalid.
     */
    public static function model(string $modelId, ?ModelConfig $modelConfig = null): ModelInterface;
    /**
     * Gets provider availability checker.
     *
     * @since 0.1.0
     *
     * @return ProviderAvailabilityInterface Provider availability checker.
     */
    public static function availability(): \WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface;
    /**
     * Gets model metadata directory.
     *
     * @since 0.1.0
     *
     * @return ModelMetadataDirectoryInterface Model metadata directory.
     */
    public static function modelMetadataDirectory(): \WordPress\AiClient\Providers\Contracts\ModelMetadataDirectoryInterface;
}
PK�e]���d��7Providers/Contracts/ModelMetadataDirectoryInterface.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Contracts;

use WordPress\AiClient\Common\Exception\InvalidArgumentException;
use WordPress\AiClient\Providers\Models\DTO\ModelMetadata;
/**
 * Interface for accessing model metadata within a provider.
 *
 * Provides methods to list, check, and retrieve model metadata
 * for all models supported by a provider.
 *
 * @since 0.1.0
 */
interface ModelMetadataDirectoryInterface
{
    /**
     * Lists all available model metadata.
     *
     * @since 0.1.0
     *
     * @return list<ModelMetadata> Array of model metadata.
     */
    public function listModelMetadata(): array;
    /**
     * Checks if metadata exists for a specific model.
     *
     * @since 0.1.0
     *
     * @param string $modelId Model identifier.
     * @return bool True if metadata exists, false otherwise.
     */
    public function hasModelMetadata(string $modelId): bool;
    /**
     * Gets metadata for a specific model.
     *
     * @since 0.1.0
     *
     * @param string $modelId Model identifier.
     * @return ModelMetadata Model metadata.
     * @throws InvalidArgumentException If model metadata not found.
     */
    public function getModelMetadata(string $modelId): ModelMetadata;
}
PK�e]ϖ��RR:Providers/Contracts/ProviderOperationsHandlerInterface.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Providers\Contracts;

use WordPress\AiClient\Common\Exception\InvalidArgumentException;
use WordPress\AiClient\Operations\Contracts\OperationInterface;
/**
 * Interface for handling provider-level operations.
 *
 * Provides methods to retrieve and manage long-running operations
 * across all models within a provider. Operations are tracked at the
 * provider level rather than per-model.
 *
 * @since 0.1.0
 */
interface ProviderOperationsHandlerInterface
{
    /**
     * Gets an operation by ID.
     *
     * @since 0.1.0
     *
     * @param string $operationId Operation identifier.
     * @return OperationInterface The operation.
     * @throws InvalidArgumentException If operation not found.
     */
    public function getOperation(string $operationId): OperationInterface;
}
PK�e]����#Events/AfterGenerateResultEvent.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Events;

use WordPress\AiClient\Messages\DTO\Message;
use WordPress\AiClient\Providers\Models\Contracts\ModelInterface;
use WordPress\AiClient\Providers\Models\Enums\CapabilityEnum;
use WordPress\AiClient\Results\DTO\GenerativeAiResult;
/**
 * Event dispatched after a prompt has been sent to the AI model and a response received.
 *
 * This event allows listeners to inspect the result of the model call for logging,
 * analytics, or other post-processing purposes. The result object is immutable.
 *
 * @since 0.4.0
 */
class AfterGenerateResultEvent
{
    /**
     * @var list<Message> The messages that were sent to the model.
     */
    private array $messages;
    /**
     * @var ModelInterface The model that processed the prompt.
     */
    private ModelInterface $model;
    /**
     * @var CapabilityEnum|null The capability that was used for generation.
     */
    private ?CapabilityEnum $capability;
    /**
     * @var GenerativeAiResult The result from the model.
     */
    private GenerativeAiResult $result;
    /**
     * Constructor.
     *
     * @since 0.4.0
     *
     * @param list<Message> $messages The messages that were sent to the model.
     * @param ModelInterface $model The model that processed the prompt.
     * @param CapabilityEnum|null $capability The capability that was used for generation.
     * @param GenerativeAiResult $result The result from the model.
     */
    public function __construct(array $messages, ModelInterface $model, ?CapabilityEnum $capability, GenerativeAiResult $result)
    {
        $this->messages = $messages;
        $this->model = $model;
        $this->capability = $capability;
        $this->result = $result;
    }
    /**
     * Gets the messages that were sent to the model.
     *
     * @since 0.4.0
     *
     * @return list<Message> The messages.
     */
    public function getMessages(): array
    {
        return $this->messages;
    }
    /**
     * Gets the model that processed the prompt.
     *
     * @since 0.4.0
     *
     * @return ModelInterface The model.
     */
    public function getModel(): ModelInterface
    {
        return $this->model;
    }
    /**
     * Gets the capability that was used for generation.
     *
     * @since 0.4.0
     *
     * @return CapabilityEnum|null The capability, or null if not specified.
     */
    public function getCapability(): ?CapabilityEnum
    {
        return $this->capability;
    }
    /**
     * Gets the result from the model.
     *
     * @since 0.4.0
     *
     * @return GenerativeAiResult The result.
     */
    public function getResult(): GenerativeAiResult
    {
        return $this->result;
    }
    /**
     * Performs a deep clone of the event.
     *
     * This method ensures that message and result objects are cloned to prevent
     * modifications to the cloned event from affecting the original.
     * The model object is not cloned as it is a service object.
     *
     * @since 0.4.2
     */
    public function __clone()
    {
        $clonedMessages = [];
        foreach ($this->messages as $message) {
            $clonedMessages[] = clone $message;
        }
        $this->messages = $clonedMessages;
        $this->result = clone $this->result;
    }
}
PK�e]xE�ک
�
$Events/BeforeGenerateResultEvent.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Events;

use WordPress\AiClient\Messages\DTO\Message;
use WordPress\AiClient\Providers\Models\Contracts\ModelInterface;
use WordPress\AiClient\Providers\Models\Enums\CapabilityEnum;
/**
 * Event dispatched before a prompt is sent to the AI model.
 *
 * This event allows listeners to inspect and modify the messages before they
 * are sent to the model. The event is not stoppable, meaning the model call
 * will always proceed regardless of listener actions.
 *
 * @since 0.4.0
 */
class BeforeGenerateResultEvent
{
    /**
     * @var list<Message> The messages to be sent to the model.
     */
    private array $messages;
    /**
     * @var ModelInterface The model that will process the prompt.
     */
    private ModelInterface $model;
    /**
     * @var CapabilityEnum|null The capability being used for generation.
     */
    private ?CapabilityEnum $capability;
    /**
     * Constructor.
     *
     * @since 0.4.0
     *
     * @param list<Message> $messages The messages to be sent to the model.
     * @param ModelInterface $model The model that will process the prompt.
     * @param CapabilityEnum|null $capability The capability being used for generation.
     */
    public function __construct(array $messages, ModelInterface $model, ?CapabilityEnum $capability)
    {
        $this->messages = $messages;
        $this->model = $model;
        $this->capability = $capability;
    }
    /**
     * Gets the messages to be sent to the model.
     *
     * @since 0.4.0
     *
     * @return list<Message> The messages.
     */
    public function getMessages(): array
    {
        return $this->messages;
    }
    /**
     * Gets the model that will process the prompt.
     *
     * @since 0.4.0
     *
     * @return ModelInterface The model.
     */
    public function getModel(): ModelInterface
    {
        return $this->model;
    }
    /**
     * Gets the capability being used for generation.
     *
     * @since 0.4.0
     *
     * @return CapabilityEnum|null The capability, or null if not specified.
     */
    public function getCapability(): ?CapabilityEnum
    {
        return $this->capability;
    }
    /**
     * Performs a deep clone of the event.
     *
     * This method ensures that message objects are cloned to prevent
     * modifications to the cloned event from affecting the original.
     * The model object is not cloned as it is a service object.
     *
     * @since 0.4.2
     */
    public function __clone()
    {
        $clonedMessages = [];
        foreach ($this->messages as $message) {
            $clonedMessages[] = clone $message;
        }
        $this->messages = $clonedMessages;
    }
}
PK�e]Ce-[[Tools/DTO/FunctionCall.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Tools\DTO;

use WordPress\AiClient\Common\AbstractDataTransferObject;
use WordPress\AiClient\Common\Exception\InvalidArgumentException;
/**
 * Represents a function call request from an AI model.
 *
 * This DTO encapsulates information about a function that the AI model
 * wants to invoke, including the function name and its arguments.
 *
 * @since 0.1.0
 *
 * @phpstan-type FunctionCallArrayShape array{id?: string, name?: string, args?: mixed}
 *
 * @extends AbstractDataTransferObject<FunctionCallArrayShape>
 */
class FunctionCall extends AbstractDataTransferObject
{
    public const KEY_ID = 'id';
    public const KEY_NAME = 'name';
    public const KEY_ARGS = 'args';
    /**
     * @var string|null Unique identifier for this function call.
     */
    private ?string $id;
    /**
     * @var string|null The name of the function to call.
     */
    private ?string $name;
    /**
     * @var mixed The arguments to pass to the function.
     */
    private $args;
    /**
     * Constructor.
     *
     * @since 0.1.0
     *
     * @param string|null $id Unique identifier for this function call.
     * @param string|null $name The name of the function to call.
     * @param mixed $args The arguments to pass to the function.
     * @throws InvalidArgumentException If neither id nor name is provided.
     */
    public function __construct(?string $id = null, ?string $name = null, $args = null)
    {
        if ($id === null && $name === null) {
            throw new InvalidArgumentException('At least one of id or name must be provided.');
        }
        $this->id = $id;
        $this->name = $name;
        $this->args = $args;
    }
    /**
     * Gets the function call ID.
     *
     * @since 0.1.0
     *
     * @return string|null The function call ID.
     */
    public function getId(): ?string
    {
        return $this->id;
    }
    /**
     * Gets the function name.
     *
     * @since 0.1.0
     *
     * @return string|null The function name.
     */
    public function getName(): ?string
    {
        return $this->name;
    }
    /**
     * Gets the function arguments.
     *
     * @since 0.1.0
     *
     * @return mixed The function arguments.
     */
    public function getArgs()
    {
        return $this->args;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function getJsonSchema(): array
    {
        return ['type' => 'object', 'properties' => [self::KEY_ID => ['type' => 'string', 'description' => 'Unique identifier for this function call.'], self::KEY_NAME => ['type' => 'string', 'description' => 'The name of the function to call.'], self::KEY_ARGS => ['type' => ['string', 'number', 'boolean', 'object', 'array', 'null'], 'description' => 'The arguments to pass to the function.']], 'anyOf' => [['required' => [self::KEY_ID]], ['required' => [self::KEY_NAME]]]];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     *
     * @return FunctionCallArrayShape
     */
    public function toArray(): array
    {
        $data = [];
        if ($this->id !== null) {
            $data[self::KEY_ID] = $this->id;
        }
        if ($this->name !== null) {
            $data[self::KEY_NAME] = $this->name;
        }
        if ($this->args !== null) {
            $data[self::KEY_ARGS] = $this->args;
        }
        return $data;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function fromArray(array $array): self
    {
        return new self($array[self::KEY_ID] ?? null, $array[self::KEY_NAME] ?? null, $array[self::KEY_ARGS] ?? null);
    }
}
PK�e]K0�q��Tools/DTO/FunctionResponse.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Tools\DTO;

use WordPress\AiClient\Common\AbstractDataTransferObject;
use WordPress\AiClient\Common\Exception\InvalidArgumentException;
/**
 * Represents a response to a function call.
 *
 * This DTO encapsulates the result of executing a function that was
 * requested by the AI model through a FunctionCall.
 *
 * @since 0.1.0
 *
 * @phpstan-type FunctionResponseArrayShape array{id?: string, name?: string, response: mixed}
 *
 * @extends AbstractDataTransferObject<FunctionResponseArrayShape>
 */
class FunctionResponse extends AbstractDataTransferObject
{
    public const KEY_ID = 'id';
    public const KEY_NAME = 'name';
    public const KEY_RESPONSE = 'response';
    /**
     * @var string|null The ID of the function call this is responding to.
     */
    private ?string $id;
    /**
     * @var string|null The name of the function that was called.
     */
    private ?string $name;
    /**
     * @var mixed The response data from the function.
     */
    private $response;
    /**
     * Constructor.
     *
     * @since 0.1.0
     *
     * @param string|null $id The ID of the function call this is responding to.
     * @param string|null $name The name of the function that was called.
     * @param mixed $response The response data from the function.
     * @throws InvalidArgumentException If neither id nor name is provided.
     */
    public function __construct(?string $id, ?string $name, $response)
    {
        if ($id === null && $name === null) {
            throw new InvalidArgumentException('At least one of id or name must be provided.');
        }
        $this->id = $id;
        $this->name = $name;
        $this->response = $response;
    }
    /**
     * Gets the function call ID.
     *
     * @since 0.1.0
     *
     * @return string|null The function call ID.
     */
    public function getId(): ?string
    {
        return $this->id;
    }
    /**
     * Gets the function name.
     *
     * @since 0.1.0
     *
     * @return string|null The function name.
     */
    public function getName(): ?string
    {
        return $this->name;
    }
    /**
     * Gets the function response.
     *
     * @since 0.1.0
     *
     * @return mixed The response data.
     */
    public function getResponse()
    {
        return $this->response;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function getJsonSchema(): array
    {
        return ['type' => 'object', 'properties' => [self::KEY_ID => ['type' => 'string', 'description' => 'The ID of the function call this is responding to.'], self::KEY_NAME => ['type' => 'string', 'description' => 'The name of the function that was called.'], self::KEY_RESPONSE => ['type' => ['string', 'number', 'boolean', 'object', 'array', 'null'], 'description' => 'The response data from the function.']], 'anyOf' => [['required' => [self::KEY_RESPONSE, self::KEY_ID]], ['required' => [self::KEY_RESPONSE, self::KEY_NAME]]]];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     *
     * @return FunctionResponseArrayShape
     */
    public function toArray(): array
    {
        $data = [];
        if ($this->id !== null) {
            $data[self::KEY_ID] = $this->id;
        }
        if ($this->name !== null) {
            $data[self::KEY_NAME] = $this->name;
        }
        $data[self::KEY_RESPONSE] = $this->response;
        return $data;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function fromArray(array $array): self
    {
        static::validateFromArrayData($array, [self::KEY_RESPONSE]);
        return new self($array[self::KEY_ID] ?? null, $array[self::KEY_NAME] ?? null, $array[self::KEY_RESPONSE]);
    }
}
PK�e]��BBTools/DTO/WebSearch.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Tools\DTO;

use WordPress\AiClient\Common\AbstractDataTransferObject;
/**
 * Represents web search configuration for AI models.
 *
 * This DTO defines constraints for web searches that AI models can perform,
 * including allowed and disallowed domains.
 *
 * @since 0.1.0
 *
 * @phpstan-type WebSearchArrayShape array{allowedDomains?: string[], disallowedDomains?: string[]}
 *
 * @extends AbstractDataTransferObject<WebSearchArrayShape>
 */
class WebSearch extends AbstractDataTransferObject
{
    public const KEY_ALLOWED_DOMAINS = 'allowedDomains';
    public const KEY_DISALLOWED_DOMAINS = 'disallowedDomains';
    /**
     * @var string[] List of domains that are allowed for web search.
     */
    private array $allowedDomains;
    /**
     * @var string[] List of domains that are disallowed for web search.
     */
    private array $disallowedDomains;
    /**
     * Constructor.
     *
     * @since 0.1.0
     *
     * @param string[] $allowedDomains List of domains that are allowed for web search.
     * @param string[] $disallowedDomains List of domains that are disallowed for web search.
     */
    public function __construct(array $allowedDomains = [], array $disallowedDomains = [])
    {
        $this->allowedDomains = $allowedDomains;
        $this->disallowedDomains = $disallowedDomains;
    }
    /**
     * Gets the allowed domains.
     *
     * @since 0.1.0
     *
     * @return string[] The allowed domains.
     */
    public function getAllowedDomains(): array
    {
        return $this->allowedDomains;
    }
    /**
     * Gets the disallowed domains.
     *
     * @since 0.1.0
     *
     * @return string[] The disallowed domains.
     */
    public function getDisallowedDomains(): array
    {
        return $this->disallowedDomains;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function getJsonSchema(): array
    {
        return ['type' => 'object', 'properties' => [self::KEY_ALLOWED_DOMAINS => ['type' => 'array', 'items' => ['type' => 'string'], 'description' => 'List of domains that are allowed for web search.'], self::KEY_DISALLOWED_DOMAINS => ['type' => 'array', 'items' => ['type' => 'string'], 'description' => 'List of domains that are disallowed for web search.']], 'required' => []];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     *
     * @return WebSearchArrayShape
     */
    public function toArray(): array
    {
        return [self::KEY_ALLOWED_DOMAINS => $this->allowedDomains, self::KEY_DISALLOWED_DOMAINS => $this->disallowedDomains];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function fromArray(array $array): self
    {
        return new self($array[self::KEY_ALLOWED_DOMAINS] ?? [], $array[self::KEY_DISALLOWED_DOMAINS] ?? []);
    }
}
PK�e]$yNG!Tools/DTO/FunctionDeclaration.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Tools\DTO;

use WordPress\AiClient\Common\AbstractDataTransferObject;
/**
 * Represents a function declaration for AI models.
 *
 * This DTO describes a function that can be called by the AI model,
 * including its name, description, and parameter schema.
 *
 * @since 0.1.0
 *
 * @phpstan-type FunctionDeclarationArrayShape array{
 *     name: string,
 *     description: string,
 *     parameters?: array<string, mixed>
 * }
 *
 * @extends AbstractDataTransferObject<FunctionDeclarationArrayShape>
 */
class FunctionDeclaration extends AbstractDataTransferObject
{
    public const KEY_NAME = 'name';
    public const KEY_DESCRIPTION = 'description';
    public const KEY_PARAMETERS = 'parameters';
    /**
     * @var string The name of the function.
     */
    private string $name;
    /**
     * @var string A description of what the function does.
     */
    private string $description;
    /**
     * @var array<string, mixed>|null The JSON schema for the function parameters.
     */
    private ?array $parameters;
    /**
     * Constructor.
     *
     * @since 0.1.0
     *
     * @param string $name The name of the function.
     * @param string $description A description of what the function does.
     * @param array<string, mixed>|null $parameters The JSON schema for the function parameters.
     */
    public function __construct(string $name, string $description, ?array $parameters = null)
    {
        $this->name = $name;
        $this->description = $description;
        $this->parameters = $parameters;
    }
    /**
     * Gets the function name.
     *
     * @since 0.1.0
     *
     * @return string The function name.
     */
    public function getName(): string
    {
        return $this->name;
    }
    /**
     * Gets the function description.
     *
     * @since 0.1.0
     *
     * @return string The function description.
     */
    public function getDescription(): string
    {
        return $this->description;
    }
    /**
     * Gets the function parameters schema.
     *
     * @since 0.1.0
     *
     * @return array<string, mixed>|null The parameters schema.
     */
    public function getParameters(): ?array
    {
        return $this->parameters;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function getJsonSchema(): array
    {
        return ['type' => 'object', 'properties' => [self::KEY_NAME => ['type' => 'string', 'description' => 'The name of the function.'], self::KEY_DESCRIPTION => ['type' => 'string', 'description' => 'A description of what the function does.'], self::KEY_PARAMETERS => ['type' => 'object', 'description' => 'The JSON schema for the function parameters.', 'additionalProperties' => \true]], 'required' => [self::KEY_NAME, self::KEY_DESCRIPTION]];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     *
     * @return FunctionDeclarationArrayShape
     */
    public function toArray(): array
    {
        $data = [self::KEY_NAME => $this->name, self::KEY_DESCRIPTION => $this->description];
        if ($this->parameters !== null) {
            $data[self::KEY_PARAMETERS] = $this->parameters;
        }
        return $data;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function fromArray(array $array): self
    {
        static::validateFromArrayData($array, [self::KEY_NAME, self::KEY_DESCRIPTION]);
        return new self($array[self::KEY_NAME], $array[self::KEY_DESCRIPTION], $array[self::KEY_PARAMETERS] ?? null);
    }
}
PK�e]��y�
�
Results/DTO/Candidate.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Results\DTO;

use WordPress\AiClient\Common\AbstractDataTransferObject;
use WordPress\AiClient\Common\Exception\InvalidArgumentException;
use WordPress\AiClient\Messages\DTO\Message;
use WordPress\AiClient\Results\Enums\FinishReasonEnum;
/**
 * Represents a candidate response from an AI model.
 *
 * When generating content, AI models can produce multiple candidates.
 * Each candidate contains a message and metadata about why generation stopped.
 *
 * @since 0.1.0
 *
 * @phpstan-import-type MessageArrayShape from Message
 *
 * @phpstan-type CandidateArrayShape array{message: MessageArrayShape, finishReason: string}
 *
 * @extends AbstractDataTransferObject<CandidateArrayShape>
 */
class Candidate extends AbstractDataTransferObject
{
    public const KEY_MESSAGE = 'message';
    public const KEY_FINISH_REASON = 'finishReason';
    /**
     * @var Message The generated message.
     */
    private Message $message;
    /**
     * @var FinishReasonEnum The reason generation stopped.
     */
    private FinishReasonEnum $finishReason;
    /**
     * Constructor.
     *
     * @since 0.1.0
     *
     * @param Message $message The generated message.
     * @param FinishReasonEnum $finishReason The reason generation stopped.
     */
    public function __construct(Message $message, FinishReasonEnum $finishReason)
    {
        if (!$message->getRole()->isModel()) {
            throw new InvalidArgumentException('Message must be a model message.');
        }
        $this->message = $message;
        $this->finishReason = $finishReason;
    }
    /**
     * Gets the generated message.
     *
     * @since 0.1.0
     *
     * @return Message The message.
     */
    public function getMessage(): Message
    {
        return $this->message;
    }
    /**
     * Gets the finish reason.
     *
     * @since 0.1.0
     *
     * @return FinishReasonEnum The finish reason.
     */
    public function getFinishReason(): FinishReasonEnum
    {
        return $this->finishReason;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function getJsonSchema(): array
    {
        return ['type' => 'object', 'properties' => [self::KEY_MESSAGE => Message::getJsonSchema(), self::KEY_FINISH_REASON => ['type' => 'string', 'enum' => FinishReasonEnum::getValues(), 'description' => 'The reason generation stopped.']], 'required' => [self::KEY_MESSAGE, self::KEY_FINISH_REASON]];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     *
     * @return CandidateArrayShape
     */
    public function toArray(): array
    {
        return [self::KEY_MESSAGE => $this->message->toArray(), self::KEY_FINISH_REASON => $this->finishReason->value];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function fromArray(array $array): self
    {
        static::validateFromArrayData($array, [self::KEY_MESSAGE, self::KEY_FINISH_REASON]);
        $messageData = $array[self::KEY_MESSAGE];
        return new self(Message::fromArray($messageData), FinishReasonEnum::from($array[self::KEY_FINISH_REASON]));
    }
    /**
     * Performs a deep clone of the candidate.
     *
     * This method ensures that the message object is cloned to prevent
     * modifications to the cloned candidate from affecting the original.
     *
     * @since 0.4.2
     */
    public function __clone()
    {
        $this->message = clone $this->message;
    }
}
PK�e]�jLResults/DTO/TokenUsage.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Results\DTO;

use WordPress\AiClient\Common\AbstractDataTransferObject;
/**
 * Represents token usage statistics for an AI operation.
 *
 * This DTO tracks the number of tokens used in prompts and completions,
 * which is important for monitoring usage and costs.
 *
 * Note that thought tokens are a subset of completion tokens, not additive.
 * In other words: completionTokens - thoughtTokens = tokens of actual output content.
 *
 * @since 0.1.0
 *
 * @phpstan-type TokenUsageArrayShape array{
 *     promptTokens: int,
 *     completionTokens: int,
 *     totalTokens: int,
 *     thoughtTokens?: int
 * }
 *
 * @extends AbstractDataTransferObject<TokenUsageArrayShape>
 */
class TokenUsage extends AbstractDataTransferObject
{
    public const KEY_PROMPT_TOKENS = 'promptTokens';
    public const KEY_COMPLETION_TOKENS = 'completionTokens';
    public const KEY_TOTAL_TOKENS = 'totalTokens';
    public const KEY_THOUGHT_TOKENS = 'thoughtTokens';
    /**
     * @var int Number of tokens in the prompt.
     */
    private int $promptTokens;
    /**
     * @var int Number of tokens in the completion, including any thought tokens.
     */
    private int $completionTokens;
    /**
     * @var int Total number of tokens used.
     */
    private int $totalTokens;
    /**
     * @var int|null Number of tokens used for thinking, as a subset of completion tokens.
     */
    private ?int $thoughtTokens;
    /**
     * Constructor.
     *
     * @since 0.1.0
     *
     * @param int $promptTokens Number of tokens in the prompt.
     * @param int $completionTokens Number of tokens in the completion, including any thought tokens.
     * @param int $totalTokens Total number of tokens used.
     * @param int|null $thoughtTokens Number of tokens used for thinking, as a subset of completion tokens.
     */
    public function __construct(int $promptTokens, int $completionTokens, int $totalTokens, ?int $thoughtTokens = null)
    {
        $this->promptTokens = $promptTokens;
        $this->completionTokens = $completionTokens;
        $this->totalTokens = $totalTokens;
        $this->thoughtTokens = $thoughtTokens;
    }
    /**
     * Gets the number of prompt tokens.
     *
     * @since 0.1.0
     *
     * @return int The prompt token count.
     */
    public function getPromptTokens(): int
    {
        return $this->promptTokens;
    }
    /**
     * Gets the number of completion tokens, including any thought tokens.
     *
     * @since 0.1.0
     *
     * @return int The completion token count.
     */
    public function getCompletionTokens(): int
    {
        return $this->completionTokens;
    }
    /**
     * Gets the total number of tokens.
     *
     * @since 0.1.0
     *
     * @return int The total token count.
     */
    public function getTotalTokens(): int
    {
        return $this->totalTokens;
    }
    /**
     * Gets the number of thought tokens, which is a subset of the completion token count.
     *
     * @since 1.3.0
     *
     * @return int|null The thought token count or null if not available.
     */
    public function getThoughtTokens(): ?int
    {
        return $this->thoughtTokens;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function getJsonSchema(): array
    {
        return ['type' => 'object', 'properties' => [self::KEY_PROMPT_TOKENS => ['type' => 'integer', 'description' => 'Number of tokens in the prompt.'], self::KEY_COMPLETION_TOKENS => ['type' => 'integer', 'description' => 'Number of tokens in the completion, including any thought tokens.'], self::KEY_TOTAL_TOKENS => ['type' => 'integer', 'description' => 'Total number of tokens used.'], self::KEY_THOUGHT_TOKENS => ['type' => 'integer', 'description' => 'Number of tokens used for thinking, as a subset of completion tokens.']], 'required' => [self::KEY_PROMPT_TOKENS, self::KEY_COMPLETION_TOKENS, self::KEY_TOTAL_TOKENS]];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     *
     * @return TokenUsageArrayShape
     */
    public function toArray(): array
    {
        $data = [self::KEY_PROMPT_TOKENS => $this->promptTokens, self::KEY_COMPLETION_TOKENS => $this->completionTokens, self::KEY_TOTAL_TOKENS => $this->totalTokens];
        if ($this->thoughtTokens !== null) {
            $data[self::KEY_THOUGHT_TOKENS] = $this->thoughtTokens;
        }
        return $data;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function fromArray(array $array): self
    {
        static::validateFromArrayData($array, [self::KEY_PROMPT_TOKENS, self::KEY_COMPLETION_TOKENS, self::KEY_TOTAL_TOKENS]);
        return new self($array[self::KEY_PROMPT_TOKENS], $array[self::KEY_COMPLETION_TOKENS], $array[self::KEY_TOTAL_TOKENS], $array[self::KEY_THOUGHT_TOKENS] ?? null);
    }
}
PK�e]�]���5�5"Results/DTO/GenerativeAiResult.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Results\DTO;

use WordPress\AiClient\Common\AbstractDataTransferObject;
use WordPress\AiClient\Common\Exception\InvalidArgumentException;
use WordPress\AiClient\Common\Exception\RuntimeException;
use WordPress\AiClient\Files\DTO\File;
use WordPress\AiClient\Messages\DTO\Message;
use WordPress\AiClient\Providers\DTO\ProviderMetadata;
use WordPress\AiClient\Providers\Models\DTO\ModelMetadata;
use WordPress\AiClient\Results\Contracts\ResultInterface;
/**
 * Represents the result of a generative AI operation.
 *
 * This DTO contains the generated candidates along with usage statistics
 * and metadata from the AI provider.
 *
 * @since 0.1.0
 *
 * @phpstan-import-type CandidateArrayShape from Candidate
 * @phpstan-import-type TokenUsageArrayShape from TokenUsage
 * @phpstan-import-type ProviderMetadataArrayShape from ProviderMetadata
 * @phpstan-import-type ModelMetadataArrayShape from ModelMetadata
 *
 * @phpstan-type GenerativeAiResultArrayShape array{
 *     id: string,
 *     candidates: array<CandidateArrayShape>,
 *     tokenUsage: TokenUsageArrayShape,
 *     providerMetadata: ProviderMetadataArrayShape,
 *     modelMetadata: ModelMetadataArrayShape,
 *     additionalData?: array<string, mixed>
 * }
 *
 * @extends AbstractDataTransferObject<GenerativeAiResultArrayShape>
 */
class GenerativeAiResult extends AbstractDataTransferObject implements ResultInterface
{
    public const KEY_ID = 'id';
    public const KEY_CANDIDATES = 'candidates';
    public const KEY_TOKEN_USAGE = 'tokenUsage';
    public const KEY_PROVIDER_METADATA = 'providerMetadata';
    public const KEY_MODEL_METADATA = 'modelMetadata';
    public const KEY_ADDITIONAL_DATA = 'additionalData';
    /**
     * @var string Unique identifier for this result.
     */
    private string $id;
    /**
     * @var Candidate[] The generated candidates.
     */
    private array $candidates;
    /**
     * @var TokenUsage Token usage statistics.
     */
    private \WordPress\AiClient\Results\DTO\TokenUsage $tokenUsage;
    /**
     * @var ProviderMetadata Provider metadata.
     */
    private ProviderMetadata $providerMetadata;
    /**
     * @var ModelMetadata Model metadata.
     */
    private ModelMetadata $modelMetadata;
    /**
     * @var array<string, mixed> Additional data.
     */
    private array $additionalData;
    /**
     * Constructor.
     *
     * @since 0.1.0
     *
     * @param string $id Unique identifier for this result.
     * @param Candidate[] $candidates The generated candidates.
     * @param TokenUsage $tokenUsage Token usage statistics.
     * @param ProviderMetadata $providerMetadata Provider metadata.
     * @param ModelMetadata $modelMetadata Model metadata.
     * @param array<string, mixed> $additionalData Additional data.
     * @throws InvalidArgumentException If no candidates provided.
     */
    public function __construct(string $id, array $candidates, \WordPress\AiClient\Results\DTO\TokenUsage $tokenUsage, ProviderMetadata $providerMetadata, ModelMetadata $modelMetadata, array $additionalData = [])
    {
        if (empty($candidates)) {
            throw new InvalidArgumentException('At least one candidate must be provided');
        }
        $this->id = $id;
        $this->candidates = $candidates;
        $this->tokenUsage = $tokenUsage;
        $this->providerMetadata = $providerMetadata;
        $this->modelMetadata = $modelMetadata;
        $this->additionalData = $additionalData;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public function getId(): string
    {
        return $this->id;
    }
    /**
     * Gets the generated candidates.
     *
     * @since 0.1.0
     *
     * @return Candidate[] The candidates.
     */
    public function getCandidates(): array
    {
        return $this->candidates;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public function getTokenUsage(): \WordPress\AiClient\Results\DTO\TokenUsage
    {
        return $this->tokenUsage;
    }
    /**
     * Gets the provider metadata.
     *
     * @since 0.1.0
     *
     * @return ProviderMetadata The provider metadata.
     */
    public function getProviderMetadata(): ProviderMetadata
    {
        return $this->providerMetadata;
    }
    /**
     * Gets the model metadata.
     *
     * @since 0.1.0
     *
     * @return ModelMetadata The model metadata.
     */
    public function getModelMetadata(): ModelMetadata
    {
        return $this->modelMetadata;
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public function getAdditionalData(): array
    {
        return $this->additionalData;
    }
    /**
     * Gets the total number of candidates.
     *
     * @since 0.1.0
     *
     * @return int The total number of candidates.
     */
    public function getCandidateCount(): int
    {
        return count($this->candidates);
    }
    /**
     * Checks if the result has multiple candidates.
     *
     * @since 0.1.0
     *
     * @return bool True if there are multiple candidates, false otherwise.
     */
    public function hasMultipleCandidates(): bool
    {
        return $this->getCandidateCount() > 1;
    }
    /**
     * Converts the first candidate to text.
     *
     * Only text from the content channel is considered. Text within model thought or reasoning is ignored.
     *
     * @since 0.1.0
     *
     * @return string The text content.
     * @throws RuntimeException If no text content.
     */
    public function toText(): string
    {
        $message = $this->candidates[0]->getMessage();
        foreach ($message->getParts() as $part) {
            $channel = $part->getChannel();
            $text = $part->getText();
            if ($channel->isContent() && $text !== null) {
                return $text;
            }
        }
        throw new RuntimeException('No text content found in first candidate');
    }
    /**
     * Converts the first candidate to a file.
     *
     * Only files from the content channel are considered. Files within model thought or reasoning are ignored.
     *
     * @since 0.1.0
     *
     * @return File The file.
     * @throws RuntimeException If no file content.
     */
    public function toFile(): File
    {
        $message = $this->candidates[0]->getMessage();
        foreach ($message->getParts() as $part) {
            $channel = $part->getChannel();
            $file = $part->getFile();
            if ($channel->isContent() && $file !== null) {
                return $file;
            }
        }
        throw new RuntimeException('No file content found in first candidate');
    }
    /**
     * Converts the first candidate to an image file.
     *
     * @since 0.1.0
     *
     * @return File The image file.
     * @throws RuntimeException If no image content.
     */
    public function toImageFile(): File
    {
        $file = $this->toFile();
        if (!$file->isImage()) {
            throw new RuntimeException(sprintf('File is not an image. MIME type: %s', $file->getMimeType()));
        }
        return $file;
    }
    /**
     * Converts the first candidate to an audio file.
     *
     * @since 0.1.0
     *
     * @return File The audio file.
     * @throws RuntimeException If no audio content.
     */
    public function toAudioFile(): File
    {
        $file = $this->toFile();
        if (!$file->isAudio()) {
            throw new RuntimeException(sprintf('File is not an audio file. MIME type: %s', $file->getMimeType()));
        }
        return $file;
    }
    /**
     * Converts the first candidate to a video file.
     *
     * @since 0.1.0
     *
     * @return File The video file.
     * @throws RuntimeException If no video content.
     */
    public function toVideoFile(): File
    {
        $file = $this->toFile();
        if (!$file->isVideo()) {
            throw new RuntimeException(sprintf('File is not a video file. MIME type: %s', $file->getMimeType()));
        }
        return $file;
    }
    /**
     * Converts the first candidate to a message.
     *
     * @since 0.1.0
     *
     * @return Message The message.
     */
    public function toMessage(): Message
    {
        return $this->candidates[0]->getMessage();
    }
    /**
     * Converts all candidates to text.
     *
     * @since 0.1.0
     *
     * @return list<string> Array of text content.
     */
    public function toTexts(): array
    {
        $texts = [];
        foreach ($this->candidates as $candidate) {
            $message = $candidate->getMessage();
            foreach ($message->getParts() as $part) {
                $channel = $part->getChannel();
                $text = $part->getText();
                if ($channel->isContent() && $text !== null) {
                    $texts[] = $text;
                    break;
                }
            }
        }
        return $texts;
    }
    /**
     * Converts all candidates to files.
     *
     * @since 0.1.0
     *
     * @return list<File> Array of files.
     */
    public function toFiles(): array
    {
        $files = [];
        foreach ($this->candidates as $candidate) {
            $message = $candidate->getMessage();
            foreach ($message->getParts() as $part) {
                $channel = $part->getChannel();
                $file = $part->getFile();
                if ($channel->isContent() && $file !== null) {
                    $files[] = $file;
                    break;
                }
            }
        }
        return $files;
    }
    /**
     * Converts all candidates to image files.
     *
     * @since 0.1.0
     *
     * @return list<File> Array of image files.
     */
    public function toImageFiles(): array
    {
        return array_values(array_filter($this->toFiles(), fn(File $file) => $file->isImage()));
    }
    /**
     * Converts all candidates to audio files.
     *
     * @since 0.1.0
     *
     * @return list<File> Array of audio files.
     */
    public function toAudioFiles(): array
    {
        return array_values(array_filter($this->toFiles(), fn(File $file) => $file->isAudio()));
    }
    /**
     * Converts all candidates to video files.
     *
     * @since 0.1.0
     *
     * @return list<File> Array of video files.
     */
    public function toVideoFiles(): array
    {
        return array_values(array_filter($this->toFiles(), fn(File $file) => $file->isVideo()));
    }
    /**
     * Converts all candidates to messages.
     *
     * @since 0.1.0
     *
     * @return list<Message> Array of messages.
     */
    public function toMessages(): array
    {
        return array_values(array_map(fn(\WordPress\AiClient\Results\DTO\Candidate $candidate) => $candidate->getMessage(), $this->candidates));
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function getJsonSchema(): array
    {
        return ['type' => 'object', 'properties' => [self::KEY_ID => ['type' => 'string', 'description' => 'Unique identifier for this result.'], self::KEY_CANDIDATES => ['type' => 'array', 'items' => \WordPress\AiClient\Results\DTO\Candidate::getJsonSchema(), 'minItems' => 1, 'description' => 'The generated candidates.'], self::KEY_TOKEN_USAGE => \WordPress\AiClient\Results\DTO\TokenUsage::getJsonSchema(), self::KEY_PROVIDER_METADATA => ProviderMetadata::getJsonSchema(), self::KEY_MODEL_METADATA => ModelMetadata::getJsonSchema(), self::KEY_ADDITIONAL_DATA => ['type' => 'object', 'additionalProperties' => \true, 'description' => 'Additional data included in the API response.']], 'required' => [self::KEY_ID, self::KEY_CANDIDATES, self::KEY_TOKEN_USAGE, self::KEY_PROVIDER_METADATA, self::KEY_MODEL_METADATA]];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     *
     * @return GenerativeAiResultArrayShape
     */
    public function toArray(): array
    {
        return [self::KEY_ID => $this->id, self::KEY_CANDIDATES => array_map(fn(\WordPress\AiClient\Results\DTO\Candidate $candidate) => $candidate->toArray(), $this->candidates), self::KEY_TOKEN_USAGE => $this->tokenUsage->toArray(), self::KEY_PROVIDER_METADATA => $this->providerMetadata->toArray(), self::KEY_MODEL_METADATA => $this->modelMetadata->toArray(), self::KEY_ADDITIONAL_DATA => $this->additionalData];
    }
    /**
     * {@inheritDoc}
     *
     * @since 0.1.0
     */
    public static function fromArray(array $array): self
    {
        static::validateFromArrayData($array, [self::KEY_ID, self::KEY_CANDIDATES, self::KEY_TOKEN_USAGE, self::KEY_PROVIDER_METADATA, self::KEY_MODEL_METADATA]);
        $candidates = array_map(fn(array $candidateData) => \WordPress\AiClient\Results\DTO\Candidate::fromArray($candidateData), $array[self::KEY_CANDIDATES]);
        return new self($array[self::KEY_ID], $candidates, \WordPress\AiClient\Results\DTO\TokenUsage::fromArray($array[self::KEY_TOKEN_USAGE]), ProviderMetadata::fromArray($array[self::KEY_PROVIDER_METADATA]), ModelMetadata::fromArray($array[self::KEY_MODEL_METADATA]), $array[self::KEY_ADDITIONAL_DATA] ?? []);
    }
    /**
     * Performs a deep clone of the result.
     *
     * This method ensures that all nested objects (candidates, token usage, metadata)
     * are cloned to prevent modifications to the cloned result from affecting the original.
     *
     * @since 0.4.2
     */
    public function __clone()
    {
        $clonedCandidates = [];
        foreach ($this->candidates as $candidate) {
            $clonedCandidates[] = clone $candidate;
        }
        $this->candidates = $clonedCandidates;
        $this->tokenUsage = clone $this->tokenUsage;
        $this->providerMetadata = clone $this->providerMetadata;
        $this->modelMetadata = clone $this->modelMetadata;
    }
}
PK�e]�)OQ��"Results/Enums/FinishReasonEnum.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Results\Enums;

use WordPress\AiClient\Common\AbstractEnum;
/**
 * Enum for finish reasons of AI generation.
 *
 * @since 0.1.0
 *
 * @method static self stop() Creates an instance for STOP reason.
 * @method static self length() Creates an instance for LENGTH reason.
 * @method static self contentFilter() Creates an instance for CONTENT_FILTER reason.
 * @method static self toolCalls() Creates an instance for TOOL_CALLS reason.
 * @method static self error() Creates an instance for ERROR reason.
 * @method bool isStop() Checks if the reason is STOP.
 * @method bool isLength() Checks if the reason is LENGTH.
 * @method bool isContentFilter() Checks if the reason is CONTENT_FILTER.
 * @method bool isToolCalls() Checks if the reason is TOOL_CALLS.
 * @method bool isError() Checks if the reason is ERROR.
 */
class FinishReasonEnum extends AbstractEnum
{
    /**
     * Generation stopped naturally.
     */
    public const STOP = 'stop';
    /**
     * Generation stopped due to max length.
     */
    public const LENGTH = 'length';
    /**
     * Generation stopped due to content filter.
     */
    public const CONTENT_FILTER = 'content_filter';
    /**
     * Generation stopped to make tool calls.
     */
    public const TOOL_CALLS = 'tool_calls';
    /**
     * Generation stopped due to error.
     */
    public const ERROR = 'error';
}
PK�e]�6�yy%Results/Contracts/ResultInterface.phpnu�[���<?php

declare (strict_types=1);
namespace WordPress\AiClient\Results\Contracts;

use WordPress\AiClient\Providers\DTO\ProviderMetadata;
use WordPress\AiClient\Providers\Models\DTO\ModelMetadata;
use WordPress\AiClient\Results\DTO\TokenUsage;
/**
 * Interface for AI operation results.
 *
 * Results contain the output from AI operations along with metadata
 * such as token usage and provider-specific information.
 *
 * @since 0.1.0
 */
interface ResultInterface
{
    /**
     * Gets the result ID.
     *
     * @since 0.1.0
     *
     * @return string The unique result identifier.
     */
    public function getId(): string;
    /**
     * Gets token usage information.
     *
     * @since 0.1.0
     *
     * @return TokenUsage Token usage statistics.
     */
    public function getTokenUsage(): TokenUsage;
    /**
     * Gets the provider metadata.
     *
     * @since 0.1.0
     *
     * @return ProviderMetadata The provider metadata.
     */
    public function getProviderMetadata(): ProviderMetadata;
    /**
     * Gets the model metadata.
     *
     * @since 0.1.0
     *
     * @return ModelMetadata The model metadata.
     */
    public function getModelMetadata(): ModelMetadata;
    /**
     * Gets provider-specific metadata.
     *
     * @since 0.1.0
     *
     * @return array<string, mixed> Provider metadata.
     */
    public function getAdditionalData(): array;
}
PK��]��\\Auth.phpnu�[���<?php
/**
 * Authentication provider interface
 *
 * @package Requests\Authentication
 */

namespace WpOrg\Requests;

use WpOrg\Requests\Hooks;

/**
 * Authentication provider interface
 *
 * Implement this interface to act as an authentication provider.
 *
 * Parameters should be passed via the constructor where possible, as this
 * makes it much easier for users to use your provider.
 *
 * @see \WpOrg\Requests\Hooks
 *
 * @package Requests\Authentication
 */
interface Auth {
	/**
	 * Register hooks as needed
	 *
	 * This method is called in {@see \WpOrg\Requests\Requests::request()} when the user
	 * has set an instance as the 'auth' option. Use this callback to register all the
	 * hooks you'll need.
	 *
	 * @see \WpOrg\Requests\Hooks::register()
	 * @param \WpOrg\Requests\Hooks $hooks Hook system
	 */
	public function register(Hooks $hooks);
}
PK��]��Z�	�	Auth/Basic.phpnu�[���<?php
/**
 * Basic Authentication provider
 *
 * @package Requests\Authentication
 */

namespace WpOrg\Requests\Auth;

use WpOrg\Requests\Auth;
use WpOrg\Requests\Exception\ArgumentCount;
use WpOrg\Requests\Exception\InvalidArgument;
use WpOrg\Requests\Hooks;

/**
 * Basic Authentication provider
 *
 * Provides a handler for Basic HTTP authentication via the Authorization
 * header.
 *
 * @package Requests\Authentication
 */
class Basic implements Auth {
	/**
	 * Username
	 *
	 * @var string
	 */
	public $user;

	/**
	 * Password
	 *
	 * @var string
	 */
	public $pass;

	/**
	 * Constructor
	 *
	 * @since 2.0 Throws an `InvalidArgument` exception.
	 * @since 2.0 Throws an `ArgumentCount` exception instead of the Requests base `Exception.
	 *
	 * @param array|null $args Array of user and password. Must have exactly two elements
	 *
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not an array or null.
	 * @throws \WpOrg\Requests\Exception\ArgumentCount   On incorrect number of array elements (`authbasicbadargs`).
	 */
	public function __construct($args = null) {
		if (is_array($args)) {
			if (count($args) !== 2) {
				throw ArgumentCount::create('an array with exactly two elements', count($args), 'authbasicbadargs');
			}

			list($this->user, $this->pass) = $args;
			return;
		}

		if ($args !== null) {
			throw InvalidArgument::create(1, '$args', 'array|null', gettype($args));
		}
	}

	/**
	 * Register the necessary callbacks
	 *
	 * @see \WpOrg\Requests\Auth\Basic::curl_before_send()
	 * @see \WpOrg\Requests\Auth\Basic::fsockopen_header()
	 * @param \WpOrg\Requests\Hooks $hooks Hook system
	 */
	public function register(Hooks $hooks) {
		$hooks->register('curl.before_send', [$this, 'curl_before_send']);
		$hooks->register('fsockopen.after_headers', [$this, 'fsockopen_header']);
	}

	/**
	 * Set cURL parameters before the data is sent
	 *
	 * @param resource|\CurlHandle $handle cURL handle
	 */
	public function curl_before_send(&$handle) {
		curl_setopt($handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
		curl_setopt($handle, CURLOPT_USERPWD, $this->getAuthString());
	}

	/**
	 * Add extra headers to the request before sending
	 *
	 * @param string $out HTTP header string
	 */
	public function fsockopen_header(&$out) {
		$out .= sprintf("Authorization: Basic %s\r\n", base64_encode($this->getAuthString()));
	}

	/**
	 * Get the authentication string (user:pass)
	 *
	 * @return string
	 */
	public function getAuthString() {
		return $this->user . ':' . $this->pass;
	}
}
PK��]ח����Response.phpnu�[���<?php
/**
 * HTTP response class
 *
 * Contains a response from \WpOrg\Requests\Requests::request()
 *
 * @package Requests
 */

namespace WpOrg\Requests;

use WpOrg\Requests\Cookie\Jar;
use WpOrg\Requests\Exception;
use WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Response\Headers;

/**
 * HTTP response class
 *
 * Contains a response from \WpOrg\Requests\Requests::request()
 *
 * @package Requests
 */
class Response {

	/**
	 * Response body
	 *
	 * @var string
	 */
	public $body = '';

	/**
	 * Raw HTTP data from the transport
	 *
	 * @var string
	 */
	public $raw = '';

	/**
	 * Headers, as an associative array
	 *
	 * @var \WpOrg\Requests\Response\Headers Array-like object representing headers
	 */
	public $headers = [];

	/**
	 * Status code, false if non-blocking
	 *
	 * @var integer|boolean
	 */
	public $status_code = false;

	/**
	 * Protocol version, false if non-blocking
	 *
	 * @var float|boolean
	 */
	public $protocol_version = false;

	/**
	 * Whether the request succeeded or not
	 *
	 * @var boolean
	 */
	public $success = false;

	/**
	 * Number of redirects the request used
	 *
	 * @var integer
	 */
	public $redirects = 0;

	/**
	 * URL requested
	 *
	 * @var string
	 */
	public $url = '';

	/**
	 * Previous requests (from redirects)
	 *
	 * @var array Array of \WpOrg\Requests\Response objects
	 */
	public $history = [];

	/**
	 * Cookies from the request
	 *
	 * @var \WpOrg\Requests\Cookie\Jar Array-like object representing a cookie jar
	 */
	public $cookies = [];

	/**
	 * Constructor
	 */
	public function __construct() {
		$this->headers = new Headers();
		$this->cookies = new Jar();
	}

	/**
	 * Is the response a redirect?
	 *
	 * @return boolean True if redirect (3xx status), false if not.
	 */
	public function is_redirect() {
		$code = $this->status_code;
		return in_array($code, [300, 301, 302, 303, 307], true) || $code > 307 && $code < 400;
	}

	/**
	 * Throws an exception if the request was not successful
	 *
	 * @param boolean $allow_redirects Set to false to throw on a 3xx as well
	 *
	 * @throws \WpOrg\Requests\Exception If `$allow_redirects` is false, and code is 3xx (`response.no_redirects`)
	 * @throws \WpOrg\Requests\Exception\Http On non-successful status code. Exception class corresponds to "Status" + code (e.g. {@see \WpOrg\Requests\Exception\Http\Status404})
	 */
	public function throw_for_status($allow_redirects = true) {
		if ($this->is_redirect()) {
			if ($allow_redirects !== true) {
				throw new Exception('Redirection not allowed', 'response.no_redirects', $this);
			}
		} elseif (!$this->success) {
			$exception = Http::get_class($this->status_code);
			throw new $exception(null, $this);
		}
	}

	/**
	 * JSON decode the response body.
	 *
	 * The method parameters are the same as those for the PHP native `json_decode()` function.
	 *
	 * @link https://php.net/json-decode
	 *
	 * @param bool|null $associative Optional. When `true`, JSON objects will be returned as associative arrays;
	 *                               When `false`, JSON objects will be returned as objects.
	 *                               When `null`, JSON objects will be returned as associative arrays
	 *                               or objects depending on whether `JSON_OBJECT_AS_ARRAY` is set in the flags.
	 *                               Defaults to `true` (in contrast to the PHP native default of `null`).
	 * @param int       $depth       Optional. Maximum nesting depth of the structure being decoded.
	 *                               Defaults to `512`.
	 * @param int       $options     Optional. Bitmask of JSON_BIGINT_AS_STRING, JSON_INVALID_UTF8_IGNORE,
	 *                               JSON_INVALID_UTF8_SUBSTITUTE, JSON_OBJECT_AS_ARRAY, JSON_THROW_ON_ERROR.
	 *                               Defaults to `0` (no options set).
	 *
	 * @return array
	 *
	 * @throws \WpOrg\Requests\Exception If `$this->body` is not valid json.
	 */
	public function decode_body($associative = true, $depth = 512, $options = 0) {
		$data = json_decode($this->body, $associative, $depth, $options);

		if (json_last_error() !== JSON_ERROR_NONE) {
			$last_error = json_last_error_msg();
			throw new Exception('Unable to parse JSON data: ' . $last_error, 'response.invalid', $this);
		}

		return $data;
	}
}
PK��]R,��;;
Cookie.phpnu�[���<?php
/**
 * Cookie storage object
 *
 * @package Requests\Cookies
 */

namespace WpOrg\Requests;

use WpOrg\Requests\Exception\InvalidArgument;
use WpOrg\Requests\Iri;
use WpOrg\Requests\Response\Headers;
use WpOrg\Requests\Utility\CaseInsensitiveDictionary;
use WpOrg\Requests\Utility\InputValidator;

/**
 * Cookie storage object
 *
 * @package Requests\Cookies
 */
class Cookie {
	/**
	 * Cookie name.
	 *
	 * @var string
	 */
	public $name;

	/**
	 * Cookie value.
	 *
	 * @var string
	 */
	public $value;

	/**
	 * Cookie attributes
	 *
	 * Valid keys are `'path'`, `'domain'`, `'expires'`, `'max-age'`, `'secure'` and
	 * `'httponly'`.
	 *
	 * @var \WpOrg\Requests\Utility\CaseInsensitiveDictionary|array Array-like object
	 */
	public $attributes = [];

	/**
	 * Cookie flags
	 *
	 * Valid keys are `'creation'`, `'last-access'`, `'persistent'` and `'host-only'`.
	 *
	 * @var array
	 */
	public $flags = [];

	/**
	 * Reference time for relative calculations
	 *
	 * This is used in place of `time()` when calculating Max-Age expiration and
	 * checking time validity.
	 *
	 * @var int
	 */
	public $reference_time = 0;

	/**
	 * Create a new cookie object
	 *
	 * @param string                                                  $name           The name of the cookie.
	 * @param string                                                  $value          The value for the cookie.
	 * @param array|\WpOrg\Requests\Utility\CaseInsensitiveDictionary $attributes Associative array of attribute data
	 * @param array                                                   $flags          The flags for the cookie.
	 *                                                                                Valid keys are `'creation'`, `'last-access'`,
	 *                                                                                `'persistent'` and `'host-only'`.
	 * @param int|null                                                $reference_time Reference time for relative calculations.
	 *
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $name argument is not a string.
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $value argument is not a string.
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $attributes argument is not an array or iterable object with array access.
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $flags argument is not an array.
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $reference_time argument is not an integer or null.
	 */
	public function __construct($name, $value, $attributes = [], $flags = [], $reference_time = null) {
		if (is_string($name) === false) {
			throw InvalidArgument::create(1, '$name', 'string', gettype($name));
		}

		if (is_string($value) === false) {
			throw InvalidArgument::create(2, '$value', 'string', gettype($value));
		}

		if (InputValidator::has_array_access($attributes) === false || InputValidator::is_iterable($attributes) === false) {
			throw InvalidArgument::create(3, '$attributes', 'array|ArrayAccess&Traversable', gettype($attributes));
		}

		if (is_array($flags) === false) {
			throw InvalidArgument::create(4, '$flags', 'array', gettype($flags));
		}

		if ($reference_time !== null && is_int($reference_time) === false) {
			throw InvalidArgument::create(5, '$reference_time', 'integer|null', gettype($reference_time));
		}

		$this->name       = $name;
		$this->value      = $value;
		$this->attributes = $attributes;
		$default_flags    = [
			'creation'    => time(),
			'last-access' => time(),
			'persistent'  => false,
			'host-only'   => true,
		];
		$this->flags      = array_merge($default_flags, $flags);

		$this->reference_time = time();
		if ($reference_time !== null) {
			$this->reference_time = $reference_time;
		}

		$this->normalize();
	}

	/**
	 * Get the cookie value
	 *
	 * Attributes and other data can be accessed via methods.
	 */
	public function __toString() {
		return $this->value;
	}

	/**
	 * Check if a cookie is expired.
	 *
	 * Checks the age against $this->reference_time to determine if the cookie
	 * is expired.
	 *
	 * @return boolean True if expired, false if time is valid.
	 */
	public function is_expired() {
		// RFC6265, s. 4.1.2.2:
		// If a cookie has both the Max-Age and the Expires attribute, the Max-
		// Age attribute has precedence and controls the expiration date of the
		// cookie.
		if (isset($this->attributes['max-age'])) {
			$max_age = $this->attributes['max-age'];
			return $max_age < $this->reference_time;
		}

		if (isset($this->attributes['expires'])) {
			$expires = $this->attributes['expires'];
			return $expires < $this->reference_time;
		}

		return false;
	}

	/**
	 * Check if a cookie is valid for a given URI
	 *
	 * @param \WpOrg\Requests\Iri $uri URI to check
	 * @return boolean Whether the cookie is valid for the given URI
	 */
	public function uri_matches(Iri $uri) {
		if (!$this->domain_matches($uri->host)) {
			return false;
		}

		if (!$this->path_matches($uri->path)) {
			return false;
		}

		return empty($this->attributes['secure']) || $uri->scheme === 'https';
	}

	/**
	 * Check if a cookie is valid for a given domain
	 *
	 * @param string $domain Domain to check
	 * @return boolean Whether the cookie is valid for the given domain
	 */
	public function domain_matches($domain) {
		if (is_string($domain) === false) {
			return false;
		}

		if (!isset($this->attributes['domain'])) {
			// Cookies created manually; cookies created by Requests will set
			// the domain to the requested domain
			return true;
		}

		$cookie_domain = $this->attributes['domain'];
		if ($cookie_domain === $domain) {
			// The cookie domain and the passed domain are identical.
			return true;
		}

		// If the cookie is marked as host-only and we don't have an exact
		// match, reject the cookie
		if ($this->flags['host-only'] === true) {
			return false;
		}

		if (strlen($domain) <= strlen($cookie_domain)) {
			// For obvious reasons, the cookie domain cannot be a suffix if the passed domain
			// is shorter than the cookie domain
			return false;
		}

		if (substr($domain, -1 * strlen($cookie_domain)) !== $cookie_domain) {
			// The cookie domain should be a suffix of the passed domain.
			return false;
		}

		$prefix = substr($domain, 0, strlen($domain) - strlen($cookie_domain));
		if (substr($prefix, -1) !== '.') {
			// The last character of the passed domain that is not included in the
			// domain string should be a %x2E (".") character.
			return false;
		}

		// The passed domain should be a host name (i.e., not an IP address).
		return !preg_match('#^(.+\.)\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$#', $domain);
	}

	/**
	 * Check if a cookie is valid for a given path
	 *
	 * From the path-match check in RFC 6265 section 5.1.4
	 *
	 * @param string $request_path Path to check
	 * @return boolean Whether the cookie is valid for the given path
	 */
	public function path_matches($request_path) {
		if (empty($request_path)) {
			// Normalize empty path to root
			$request_path = '/';
		}

		if (!isset($this->attributes['path'])) {
			// Cookies created manually; cookies created by Requests will set
			// the path to the requested path
			return true;
		}

		if (is_scalar($request_path) === false) {
			return false;
		}

		$cookie_path = $this->attributes['path'];

		if ($cookie_path === $request_path) {
			// The cookie-path and the request-path are identical.
			return true;
		}

		if (strlen($request_path) > strlen($cookie_path) && substr($request_path, 0, strlen($cookie_path)) === $cookie_path) {
			if (substr($cookie_path, -1) === '/') {
				// The cookie-path is a prefix of the request-path, and the last
				// character of the cookie-path is %x2F ("/").
				return true;
			}

			if (substr($request_path, strlen($cookie_path), 1) === '/') {
				// The cookie-path is a prefix of the request-path, and the
				// first character of the request-path that is not included in
				// the cookie-path is a %x2F ("/") character.
				return true;
			}
		}

		return false;
	}

	/**
	 * Normalize cookie and attributes
	 *
	 * @return boolean Whether the cookie was successfully normalized
	 */
	public function normalize() {
		foreach ($this->attributes as $key => $value) {
			$orig_value = $value;

			if (is_string($key)) {
				$value = $this->normalize_attribute($key, $value);
			}

			if ($value === null) {
				unset($this->attributes[$key]);
				continue;
			}

			if ($value !== $orig_value) {
				$this->attributes[$key] = $value;
			}
		}

		return true;
	}

	/**
	 * Parse an individual cookie attribute
	 *
	 * Handles parsing individual attributes from the cookie values.
	 *
	 * @param string $name Attribute name
	 * @param string|int|bool $value Attribute value (string/integer value, or true if empty/flag)
	 * @return mixed Value if available, or null if the attribute value is invalid (and should be skipped)
	 */
	protected function normalize_attribute($name, $value) {
		switch (strtolower($name)) {
			case 'expires':
				// Expiration parsing, as per RFC 6265 section 5.2.1
				if (is_int($value)) {
					return $value;
				}

				$expiry_time = strtotime($value);
				if ($expiry_time === false) {
					return null;
				}

				return $expiry_time;

			case 'max-age':
				// Expiration parsing, as per RFC 6265 section 5.2.2
				if (is_int($value)) {
					return $value;
				}

				// Check that we have a valid age
				if (!preg_match('/^-?\d+$/', $value)) {
					return null;
				}

				$delta_seconds = (int) $value;
				if ($delta_seconds <= 0) {
					$expiry_time = 0;
				} else {
					$expiry_time = $this->reference_time + $delta_seconds;
				}

				return $expiry_time;

			case 'domain':
				// Domains are not required as per RFC 6265 section 5.2.3
				if (empty($value)) {
					return null;
				}

				// Domain normalization, as per RFC 6265 section 5.2.3
				if ($value[0] === '.') {
					$value = substr($value, 1);
				}

				return $value;

			default:
				return $value;
		}
	}

	/**
	 * Format a cookie for a Cookie header
	 *
	 * This is used when sending cookies to a server.
	 *
	 * @return string Cookie formatted for Cookie header
	 */
	public function format_for_header() {
		return sprintf('%s=%s', $this->name, $this->value);
	}

	/**
	 * Format a cookie for a Set-Cookie header
	 *
	 * This is used when sending cookies to clients. This isn't really
	 * applicable to client-side usage, but might be handy for debugging.
	 *
	 * @return string Cookie formatted for Set-Cookie header
	 */
	public function format_for_set_cookie() {
		$header_value = $this->format_for_header();
		if (!empty($this->attributes)) {
			$parts = [];
			foreach ($this->attributes as $key => $value) {
				// Ignore non-associative attributes
				if (is_numeric($key)) {
					$parts[] = $value;
				} else {
					$parts[] = sprintf('%s=%s', $key, $value);
				}
			}

			$header_value .= '; ' . implode('; ', $parts);
		}

		return $header_value;
	}

	/**
	 * Parse a cookie string into a cookie object
	 *
	 * Based on Mozilla's parsing code in Firefox and related projects, which
	 * is an intentional deviation from RFC 2109 and RFC 2616. RFC 6265
	 * specifies some of this handling, but not in a thorough manner.
	 *
	 * @param string $cookie_header Cookie header value (from a Set-Cookie header)
	 * @param string $name
	 * @param int|null $reference_time
	 * @return \WpOrg\Requests\Cookie Parsed cookie object
	 *
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $cookie_header argument is not a string.
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $name argument is not a string.
	 */
	public static function parse($cookie_header, $name = '', $reference_time = null) {
		if (is_string($cookie_header) === false) {
			throw InvalidArgument::create(1, '$cookie_header', 'string', gettype($cookie_header));
		}

		if (is_string($name) === false) {
			throw InvalidArgument::create(2, '$name', 'string', gettype($name));
		}

		$parts   = explode(';', $cookie_header);
		$kvparts = array_shift($parts);

		if (!empty($name)) {
			$value = $cookie_header;
		} elseif (strpos($kvparts, '=') === false) {
			// Some sites might only have a value without the equals separator.
			// Deviate from RFC 6265 and pretend it was actually a blank name
			// (`=foo`)
			//
			// https://bugzilla.mozilla.org/show_bug.cgi?id=169091
			$name  = '';
			$value = $kvparts;
		} else {
			list($name, $value) = explode('=', $kvparts, 2);
		}

		$name  = trim($name);
		$value = trim($value);

		// Attribute keys are handled case-insensitively
		$attributes = new CaseInsensitiveDictionary();

		if (!empty($parts)) {
			foreach ($parts as $part) {
				if (strpos($part, '=') === false) {
					$part_key   = $part;
					$part_value = true;
				} else {
					list($part_key, $part_value) = explode('=', $part, 2);
					$part_value                  = trim($part_value);
				}

				$part_key              = trim($part_key);
				$attributes[$part_key] = $part_value;
			}
		}

		return new static($name, $value, $attributes, [], $reference_time);
	}

	/**
	 * Parse all Set-Cookie headers from request headers
	 *
	 * @param \WpOrg\Requests\Response\Headers $headers Headers to parse from
	 * @param \WpOrg\Requests\Iri|null $origin URI for comparing cookie origins
	 * @param int|null $time Reference time for expiration calculation
	 * @return array
	 */
	public static function parse_from_headers(Headers $headers, Iri $origin = null, $time = null) {
		$cookie_headers = $headers->getValues('Set-Cookie');
		if (empty($cookie_headers)) {
			return [];
		}

		$cookies = [];
		foreach ($cookie_headers as $header) {
			$parsed = self::parse($header, '', $time);

			// Default domain/path attributes
			if (empty($parsed->attributes['domain']) && !empty($origin)) {
				$parsed->attributes['domain'] = $origin->host;
				$parsed->flags['host-only']   = true;
			} else {
				$parsed->flags['host-only'] = false;
			}

			$path_is_valid = (!empty($parsed->attributes['path']) && $parsed->attributes['path'][0] === '/');
			if (!$path_is_valid && !empty($origin)) {
				$path = $origin->path;

				// Default path normalization as per RFC 6265 section 5.1.4
				if (substr($path, 0, 1) !== '/') {
					// If the uri-path is empty or if the first character of
					// the uri-path is not a %x2F ("/") character, output
					// %x2F ("/") and skip the remaining steps.
					$path = '/';
				} elseif (substr_count($path, '/') === 1) {
					// If the uri-path contains no more than one %x2F ("/")
					// character, output %x2F ("/") and skip the remaining
					// step.
					$path = '/';
				} else {
					// Output the characters of the uri-path from the first
					// character up to, but not including, the right-most
					// %x2F ("/").
					$path = substr($path, 0, strrpos($path, '/'));
				}

				$parsed->attributes['path'] = $path;
			}

			// Reject invalid cookie domains
			if (!empty($origin) && !$parsed->domain_matches($origin->host)) {
				continue;
			}

			$cookies[$parsed->name] = $parsed;
		}

		return $cookies;
	}
}
PK��]�j;�sLsLTransport/Curl.phpnu�[���<?php
/**
 * cURL HTTP transport
 *
 * @package Requests\Transport
 */

namespace WpOrg\Requests\Transport;

use RecursiveArrayIterator;
use RecursiveIteratorIterator;
use WpOrg\Requests\Capability;
use WpOrg\Requests\Exception;
use WpOrg\Requests\Exception\InvalidArgument;
use WpOrg\Requests\Exception\Transport\Curl as CurlException;
use WpOrg\Requests\Requests;
use WpOrg\Requests\Transport;
use WpOrg\Requests\Utility\InputValidator;

/**
 * cURL HTTP transport
 *
 * @package Requests\Transport
 */
final class Curl implements Transport {
	const CURL_7_10_5 = 0x070A05;
	const CURL_7_16_2 = 0x071002;

	/**
	 * Raw HTTP data
	 *
	 * @var string
	 */
	public $headers = '';

	/**
	 * Raw body data
	 *
	 * @var string
	 */
	public $response_data = '';

	/**
	 * Information on the current request
	 *
	 * @var array cURL information array, see {@link https://www.php.net/curl_getinfo}
	 */
	public $info;

	/**
	 * cURL version number
	 *
	 * @var int
	 */
	public $version;

	/**
	 * cURL handle
	 *
	 * @var resource|\CurlHandle Resource in PHP < 8.0, Instance of CurlHandle in PHP >= 8.0.
	 */
	private $handle;

	/**
	 * Hook dispatcher instance
	 *
	 * @var \WpOrg\Requests\Hooks
	 */
	private $hooks;

	/**
	 * Have we finished the headers yet?
	 *
	 * @var boolean
	 */
	private $done_headers = false;

	/**
	 * If streaming to a file, keep the file pointer
	 *
	 * @var resource
	 */
	private $stream_handle;

	/**
	 * How many bytes are in the response body?
	 *
	 * @var int
	 */
	private $response_bytes;

	/**
	 * What's the maximum number of bytes we should keep?
	 *
	 * @var int|bool Byte count, or false if no limit.
	 */
	private $response_byte_limit;

	/**
	 * Constructor
	 */
	public function __construct() {
		$curl          = curl_version();
		$this->version = $curl['version_number'];
		$this->handle  = curl_init();

		curl_setopt($this->handle, CURLOPT_HEADER, false);
		curl_setopt($this->handle, CURLOPT_RETURNTRANSFER, 1);
		if ($this->version >= self::CURL_7_10_5) {
			curl_setopt($this->handle, CURLOPT_ENCODING, '');
		}

		if (defined('CURLOPT_PROTOCOLS')) {
			// phpcs:ignore PHPCompatibility.Constants.NewConstants.curlopt_protocolsFound
			curl_setopt($this->handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
		}

		if (defined('CURLOPT_REDIR_PROTOCOLS')) {
			// phpcs:ignore PHPCompatibility.Constants.NewConstants.curlopt_redir_protocolsFound
			curl_setopt($this->handle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
		}
	}

	/**
	 * Destructor
	 */
	public function __destruct() {
		if (is_resource($this->handle)) {
			curl_close($this->handle);
		}
	}

	/**
	 * Perform a request
	 *
	 * @param string|Stringable $url URL to request
	 * @param array $headers Associative array of request headers
	 * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
	 * @param array $options Request options, see {@see \WpOrg\Requests\Requests::response()} for documentation
	 * @return string Raw HTTP result
	 *
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $url argument is not a string or Stringable.
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $headers argument is not an array.
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $data parameter is not an array or string.
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array.
	 * @throws \WpOrg\Requests\Exception       On a cURL error (`curlerror`)
	 */
	public function request($url, $headers = [], $data = [], $options = []) {
		if (InputValidator::is_string_or_stringable($url) === false) {
			throw InvalidArgument::create(1, '$url', 'string|Stringable', gettype($url));
		}

		if (is_array($headers) === false) {
			throw InvalidArgument::create(2, '$headers', 'array', gettype($headers));
		}

		if (!is_array($data) && !is_string($data)) {
			if ($data === null) {
				$data = '';
			} else {
				throw InvalidArgument::create(3, '$data', 'array|string', gettype($data));
			}
		}

		if (is_array($options) === false) {
			throw InvalidArgument::create(4, '$options', 'array', gettype($options));
		}

		$this->hooks = $options['hooks'];

		$this->setup_handle($url, $headers, $data, $options);

		$options['hooks']->dispatch('curl.before_send', [&$this->handle]);

		if ($options['filename'] !== false) {
			// phpcs:ignore WordPress.PHP.NoSilencedErrors -- Silenced the PHP native warning in favour of throwing an exception.
			$this->stream_handle = @fopen($options['filename'], 'wb');
			if ($this->stream_handle === false) {
				$error = error_get_last();
				throw new Exception($error['message'], 'fopen');
			}
		}

		$this->response_data       = '';
		$this->response_bytes      = 0;
		$this->response_byte_limit = false;
		if ($options['max_bytes'] !== false) {
			$this->response_byte_limit = $options['max_bytes'];
		}

		if (isset($options['verify'])) {
			if ($options['verify'] === false) {
				curl_setopt($this->handle, CURLOPT_SSL_VERIFYHOST, 0);
				curl_setopt($this->handle, CURLOPT_SSL_VERIFYPEER, 0);
			} elseif (is_string($options['verify'])) {
				curl_setopt($this->handle, CURLOPT_CAINFO, $options['verify']);
			}
		}

		if (isset($options['verifyname']) && $options['verifyname'] === false) {
			curl_setopt($this->handle, CURLOPT_SSL_VERIFYHOST, 0);
		}

		curl_exec($this->handle);
		$response = $this->response_data;

		$options['hooks']->dispatch('curl.after_send', []);

		if (curl_errno($this->handle) === CURLE_WRITE_ERROR || curl_errno($this->handle) === CURLE_BAD_CONTENT_ENCODING) {
			// Reset encoding and try again
			curl_setopt($this->handle, CURLOPT_ENCODING, 'none');

			$this->response_data  = '';
			$this->response_bytes = 0;
			curl_exec($this->handle);
			$response = $this->response_data;
		}

		$this->process_response($response, $options);

		// Need to remove the $this reference from the curl handle.
		// Otherwise \WpOrg\Requests\Transport\Curl won't be garbage collected and the curl_close() will never be called.
		curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, null);
		curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, null);

		return $this->headers;
	}

	/**
	 * Send multiple requests simultaneously
	 *
	 * @param array $requests Request data
	 * @param array $options Global options
	 * @return array Array of \WpOrg\Requests\Response objects (may contain \WpOrg\Requests\Exception or string responses as well)
	 *
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $requests argument is not an array or iterable object with array access.
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array.
	 */
	public function request_multiple($requests, $options) {
		// If you're not requesting, we can't get any responses ¯\_(ツ)_/¯
		if (empty($requests)) {
			return [];
		}

		if (InputValidator::has_array_access($requests) === false || InputValidator::is_iterable($requests) === false) {
			throw InvalidArgument::create(1, '$requests', 'array|ArrayAccess&Traversable', gettype($requests));
		}

		if (is_array($options) === false) {
			throw InvalidArgument::create(2, '$options', 'array', gettype($options));
		}

		$multihandle = curl_multi_init();
		$subrequests = [];
		$subhandles  = [];

		$class = get_class($this);
		foreach ($requests as $id => $request) {
			$subrequests[$id] = new $class();
			$subhandles[$id]  = $subrequests[$id]->get_subrequest_handle($request['url'], $request['headers'], $request['data'], $request['options']);
			$request['options']['hooks']->dispatch('curl.before_multi_add', [&$subhandles[$id]]);
			curl_multi_add_handle($multihandle, $subhandles[$id]);
		}

		$completed       = 0;
		$responses       = [];
		$subrequestcount = count($subrequests);

		$request['options']['hooks']->dispatch('curl.before_multi_exec', [&$multihandle]);

		do {
			$active = 0;

			do {
				$status = curl_multi_exec($multihandle, $active);
			} while ($status === CURLM_CALL_MULTI_PERFORM);

			$to_process = [];

			// Read the information as needed
			while ($done = curl_multi_info_read($multihandle)) {
				$key = array_search($done['handle'], $subhandles, true);
				if (!isset($to_process[$key])) {
					$to_process[$key] = $done;
				}
			}

			// Parse the finished requests before we start getting the new ones
			foreach ($to_process as $key => $done) {
				$options = $requests[$key]['options'];
				if ($done['result'] !== CURLE_OK) {
					//get error string for handle.
					$reason          = curl_error($done['handle']);
					$exception       = new CurlException(
						$reason,
						CurlException::EASY,
						$done['handle'],
						$done['result']
					);
					$responses[$key] = $exception;
					$options['hooks']->dispatch('transport.internal.parse_error', [&$responses[$key], $requests[$key]]);
				} else {
					$responses[$key] = $subrequests[$key]->process_response($subrequests[$key]->response_data, $options);

					$options['hooks']->dispatch('transport.internal.parse_response', [&$responses[$key], $requests[$key]]);
				}

				curl_multi_remove_handle($multihandle, $done['handle']);
				curl_close($done['handle']);

				if (!is_string($responses[$key])) {
					$options['hooks']->dispatch('multiple.request.complete', [&$responses[$key], $key]);
				}

				$completed++;
			}
		} while ($active || $completed < $subrequestcount);

		$request['options']['hooks']->dispatch('curl.after_multi_exec', [&$multihandle]);

		curl_multi_close($multihandle);

		return $responses;
	}

	/**
	 * Get the cURL handle for use in a multi-request
	 *
	 * @param string $url URL to request
	 * @param array $headers Associative array of request headers
	 * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
	 * @param array $options Request options, see {@see \WpOrg\Requests\Requests::response()} for documentation
	 * @return resource|\CurlHandle Subrequest's cURL handle
	 */
	public function &get_subrequest_handle($url, $headers, $data, $options) {
		$this->setup_handle($url, $headers, $data, $options);

		if ($options['filename'] !== false) {
			$this->stream_handle = fopen($options['filename'], 'wb');
		}

		$this->response_data       = '';
		$this->response_bytes      = 0;
		$this->response_byte_limit = false;
		if ($options['max_bytes'] !== false) {
			$this->response_byte_limit = $options['max_bytes'];
		}

		$this->hooks = $options['hooks'];

		return $this->handle;
	}

	/**
	 * Setup the cURL handle for the given data
	 *
	 * @param string $url URL to request
	 * @param array $headers Associative array of request headers
	 * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
	 * @param array $options Request options, see {@see \WpOrg\Requests\Requests::response()} for documentation
	 */
	private function setup_handle($url, $headers, $data, $options) {
		$options['hooks']->dispatch('curl.before_request', [&$this->handle]);

		// Force closing the connection for old versions of cURL (<7.22).
		if (!isset($headers['Connection'])) {
			$headers['Connection'] = 'close';
		}

		/**
		 * Add "Expect" header.
		 *
		 * By default, cURL adds a "Expect: 100-Continue" to most requests. This header can
		 * add as much as a second to the time it takes for cURL to perform a request. To
		 * prevent this, we need to set an empty "Expect" header. To match the behaviour of
		 * Guzzle, we'll add the empty header to requests that are smaller than 1 MB and use
		 * HTTP/1.1.
		 *
		 * https://curl.se/mail/lib-2017-07/0013.html
		 */
		if (!isset($headers['Expect']) && $options['protocol_version'] === 1.1) {
			$headers['Expect'] = $this->get_expect_header($data);
		}

		$headers = Requests::flatten($headers);

		if (!empty($data)) {
			$data_format = $options['data_format'];

			if ($data_format === 'query') {
				$url  = self::format_get($url, $data);
				$data = '';
			} elseif (!is_string($data)) {
				$data = http_build_query($data, '', '&');
			}
		}

		switch ($options['type']) {
			case Requests::POST:
				curl_setopt($this->handle, CURLOPT_POST, true);
				curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data);
				break;
			case Requests::HEAD:
				curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']);
				curl_setopt($this->handle, CURLOPT_NOBODY, true);
				break;
			case Requests::TRACE:
				curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']);
				break;
			case Requests::PATCH:
			case Requests::PUT:
			case Requests::DELETE:
			case Requests::OPTIONS:
			default:
				curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']);
				if (!empty($data)) {
					curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data);
				}
		}

		// cURL requires a minimum timeout of 1 second when using the system
		// DNS resolver, as it uses `alarm()`, which is second resolution only.
		// There's no way to detect which DNS resolver is being used from our
		// end, so we need to round up regardless of the supplied timeout.
		//
		// https://github.com/curl/curl/blob/4f45240bc84a9aa648c8f7243be7b79e9f9323a5/lib/hostip.c#L606-L609
		$timeout = max($options['timeout'], 1);

		if (is_int($timeout) || $this->version < self::CURL_7_16_2) {
			curl_setopt($this->handle, CURLOPT_TIMEOUT, ceil($timeout));
		} else {
			// phpcs:ignore PHPCompatibility.Constants.NewConstants.curlopt_timeout_msFound
			curl_setopt($this->handle, CURLOPT_TIMEOUT_MS, round($timeout * 1000));
		}

		if (is_int($options['connect_timeout']) || $this->version < self::CURL_7_16_2) {
			curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT, ceil($options['connect_timeout']));
		} else {
			// phpcs:ignore PHPCompatibility.Constants.NewConstants.curlopt_connecttimeout_msFound
			curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT_MS, round($options['connect_timeout'] * 1000));
		}

		curl_setopt($this->handle, CURLOPT_URL, $url);
		curl_setopt($this->handle, CURLOPT_USERAGENT, $options['useragent']);
		if (!empty($headers)) {
			curl_setopt($this->handle, CURLOPT_HTTPHEADER, $headers);
		}

		if ($options['protocol_version'] === 1.1) {
			curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
		} else {
			curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
		}

		if ($options['blocking'] === true) {
			curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, [$this, 'stream_headers']);
			curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, [$this, 'stream_body']);
			curl_setopt($this->handle, CURLOPT_BUFFERSIZE, Requests::BUFFER_SIZE);
		}
	}

	/**
	 * Process a response
	 *
	 * @param string $response Response data from the body
	 * @param array $options Request options
	 * @return string|false HTTP response data including headers. False if non-blocking.
	 * @throws \WpOrg\Requests\Exception If the request resulted in a cURL error.
	 */
	public function process_response($response, $options) {
		if ($options['blocking'] === false) {
			$fake_headers = '';
			$options['hooks']->dispatch('curl.after_request', [&$fake_headers]);
			return false;
		}

		if ($options['filename'] !== false && $this->stream_handle) {
			fclose($this->stream_handle);
			$this->headers = trim($this->headers);
		} else {
			$this->headers .= $response;
		}

		if (curl_errno($this->handle)) {
			$error = sprintf(
				'cURL error %s: %s',
				curl_errno($this->handle),
				curl_error($this->handle)
			);
			throw new Exception($error, 'curlerror', $this->handle);
		}

		$this->info = curl_getinfo($this->handle);

		$options['hooks']->dispatch('curl.after_request', [&$this->headers, &$this->info]);
		return $this->headers;
	}

	/**
	 * Collect the headers as they are received
	 *
	 * @param resource|\CurlHandle $handle cURL handle
	 * @param string $headers Header string
	 * @return integer Length of provided header
	 */
	public function stream_headers($handle, $headers) {
		// Why do we do this? cURL will send both the final response and any
		// interim responses, such as a 100 Continue. We don't need that.
		// (We may want to keep this somewhere just in case)
		if ($this->done_headers) {
			$this->headers      = '';
			$this->done_headers = false;
		}

		$this->headers .= $headers;

		if ($headers === "\r\n") {
			$this->done_headers = true;
		}

		return strlen($headers);
	}

	/**
	 * Collect data as it's received
	 *
	 * @since 1.6.1
	 *
	 * @param resource|\CurlHandle $handle cURL handle
	 * @param string $data Body data
	 * @return integer Length of provided data
	 */
	public function stream_body($handle, $data) {
		$this->hooks->dispatch('request.progress', [$data, $this->response_bytes, $this->response_byte_limit]);
		$data_length = strlen($data);

		// Are we limiting the response size?
		if ($this->response_byte_limit) {
			if ($this->response_bytes === $this->response_byte_limit) {
				// Already at maximum, move on
				return $data_length;
			}

			if (($this->response_bytes + $data_length) > $this->response_byte_limit) {
				// Limit the length
				$limited_length = ($this->response_byte_limit - $this->response_bytes);
				$data           = substr($data, 0, $limited_length);
			}
		}

		if ($this->stream_handle) {
			fwrite($this->stream_handle, $data);
		} else {
			$this->response_data .= $data;
		}

		$this->response_bytes += strlen($data);
		return $data_length;
	}

	/**
	 * Format a URL given GET data
	 *
	 * @param string       $url  Original URL.
	 * @param array|object $data Data to build query using, see {@link https://www.php.net/http_build_query}
	 * @return string URL with data
	 */
	private static function format_get($url, $data) {
		if (!empty($data)) {
			$query     = '';
			$url_parts = parse_url($url);
			if (empty($url_parts['query'])) {
				$url_parts['query'] = '';
			} else {
				$query = $url_parts['query'];
			}

			$query .= '&' . http_build_query($data, '', '&');
			$query  = trim($query, '&');

			if (empty($url_parts['query'])) {
				$url .= '?' . $query;
			} else {
				$url = str_replace($url_parts['query'], $query, $url);
			}
		}

		return $url;
	}

	/**
	 * Self-test whether the transport can be used.
	 *
	 * The available capabilities to test for can be found in {@see \WpOrg\Requests\Capability}.
	 *
	 * @codeCoverageIgnore
	 * @param array<string, bool> $capabilities Optional. Associative array of capabilities to test against, i.e. `['<capability>' => true]`.
	 * @return bool Whether the transport can be used.
	 */
	public static function test($capabilities = []) {
		if (!function_exists('curl_init') || !function_exists('curl_exec')) {
			return false;
		}

		// If needed, check that our installed curl version supports SSL
		if (isset($capabilities[Capability::SSL]) && $capabilities[Capability::SSL]) {
			$curl_version = curl_version();
			if (!(CURL_VERSION_SSL & $curl_version['features'])) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Get the correct "Expect" header for the given request data.
	 *
	 * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD.
	 * @return string The "Expect" header.
	 */
	private function get_expect_header($data) {
		if (!is_array($data)) {
			return strlen((string) $data) >= 1048576 ? '100-Continue' : '';
		}

		$bytesize = 0;
		$iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($data));

		foreach ($iterator as $datum) {
			$bytesize += strlen((string) $datum);

			if ($bytesize >= 1048576) {
				return '100-Continue';
			}
		}

		return '';
	}
}
PK��]k��<�<Transport/Fsockopen.phpnu�[���<?php
/**
 * fsockopen HTTP transport
 *
 * @package Requests\Transport
 */

namespace WpOrg\Requests\Transport;

use WpOrg\Requests\Capability;
use WpOrg\Requests\Exception;
use WpOrg\Requests\Exception\InvalidArgument;
use WpOrg\Requests\Port;
use WpOrg\Requests\Requests;
use WpOrg\Requests\Ssl;
use WpOrg\Requests\Transport;
use WpOrg\Requests\Utility\CaseInsensitiveDictionary;
use WpOrg\Requests\Utility\InputValidator;

/**
 * fsockopen HTTP transport
 *
 * @package Requests\Transport
 */
final class Fsockopen implements Transport {
	/**
	 * Second to microsecond conversion
	 *
	 * @var integer
	 */
	const SECOND_IN_MICROSECONDS = 1000000;

	/**
	 * Raw HTTP data
	 *
	 * @var string
	 */
	public $headers = '';

	/**
	 * Stream metadata
	 *
	 * @var array Associative array of properties, see {@link https://www.php.net/stream_get_meta_data}
	 */
	public $info;

	/**
	 * What's the maximum number of bytes we should keep?
	 *
	 * @var int|bool Byte count, or false if no limit.
	 */
	private $max_bytes = false;

	/**
	 * Cache for received connection errors.
	 *
	 * @var string
	 */
	private $connect_error = '';

	/**
	 * Perform a request
	 *
	 * @param string|Stringable $url URL to request
	 * @param array $headers Associative array of request headers
	 * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
	 * @param array $options Request options, see {@see \WpOrg\Requests\Requests::response()} for documentation
	 * @return string Raw HTTP result
	 *
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $url argument is not a string or Stringable.
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $headers argument is not an array.
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $data parameter is not an array or string.
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array.
	 * @throws \WpOrg\Requests\Exception       On failure to connect to socket (`fsockopenerror`)
	 * @throws \WpOrg\Requests\Exception       On socket timeout (`timeout`)
	 */
	public function request($url, $headers = [], $data = [], $options = []) {
		if (InputValidator::is_string_or_stringable($url) === false) {
			throw InvalidArgument::create(1, '$url', 'string|Stringable', gettype($url));
		}

		if (is_array($headers) === false) {
			throw InvalidArgument::create(2, '$headers', 'array', gettype($headers));
		}

		if (!is_array($data) && !is_string($data)) {
			if ($data === null) {
				$data = '';
			} else {
				throw InvalidArgument::create(3, '$data', 'array|string', gettype($data));
			}
		}

		if (is_array($options) === false) {
			throw InvalidArgument::create(4, '$options', 'array', gettype($options));
		}

		$options['hooks']->dispatch('fsockopen.before_request');

		$url_parts = parse_url($url);
		if (empty($url_parts)) {
			throw new Exception('Invalid URL.', 'invalidurl', $url);
		}

		$host                     = $url_parts['host'];
		$context                  = stream_context_create();
		$verifyname               = false;
		$case_insensitive_headers = new CaseInsensitiveDictionary($headers);

		// HTTPS support
		if (isset($url_parts['scheme']) && strtolower($url_parts['scheme']) === 'https') {
			$remote_socket = 'ssl://' . $host;
			if (!isset($url_parts['port'])) {
				$url_parts['port'] = Port::HTTPS;
			}

			$context_options = [
				'verify_peer'       => true,
				'capture_peer_cert' => true,
			];
			$verifyname      = true;

			// SNI, if enabled (OpenSSL >=0.9.8j)
			// phpcs:ignore PHPCompatibility.Constants.NewConstants.openssl_tlsext_server_nameFound
			if (defined('OPENSSL_TLSEXT_SERVER_NAME') && OPENSSL_TLSEXT_SERVER_NAME) {
				$context_options['SNI_enabled'] = true;
				if (isset($options['verifyname']) && $options['verifyname'] === false) {
					$context_options['SNI_enabled'] = false;
				}
			}

			if (isset($options['verify'])) {
				if ($options['verify'] === false) {
					$context_options['verify_peer']      = false;
					$context_options['verify_peer_name'] = false;
					$verifyname                          = false;
				} elseif (is_string($options['verify'])) {
					$context_options['cafile'] = $options['verify'];
				}
			}

			if (isset($options['verifyname']) && $options['verifyname'] === false) {
				$context_options['verify_peer_name'] = false;
				$verifyname                          = false;
			}

			stream_context_set_option($context, ['ssl' => $context_options]);
		} else {
			$remote_socket = 'tcp://' . $host;
		}

		$this->max_bytes = $options['max_bytes'];

		if (!isset($url_parts['port'])) {
			$url_parts['port'] = Port::HTTP;
		}

		$remote_socket .= ':' . $url_parts['port'];

		// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler
		set_error_handler([$this, 'connect_error_handler'], E_WARNING | E_NOTICE);

		$options['hooks']->dispatch('fsockopen.remote_socket', [&$remote_socket]);

		$socket = stream_socket_client($remote_socket, $errno, $errstr, ceil($options['connect_timeout']), STREAM_CLIENT_CONNECT, $context);

		restore_error_handler();

		if ($verifyname && !$this->verify_certificate_from_context($host, $context)) {
			throw new Exception('SSL certificate did not match the requested domain name', 'ssl.no_match');
		}

		if (!$socket) {
			if ($errno === 0) {
				// Connection issue
				throw new Exception(rtrim($this->connect_error), 'fsockopen.connect_error');
			}

			throw new Exception($errstr, 'fsockopenerror', null, $errno);
		}

		$data_format = $options['data_format'];

		if ($data_format === 'query') {
			$path = self::format_get($url_parts, $data);
			$data = '';
		} else {
			$path = self::format_get($url_parts, []);
		}

		$options['hooks']->dispatch('fsockopen.remote_host_path', [&$path, $url]);

		$request_body = '';
		$out          = sprintf("%s %s HTTP/%.1F\r\n", $options['type'], $path, $options['protocol_version']);

		if ($options['type'] !== Requests::TRACE) {
			if (is_array($data)) {
				$request_body = http_build_query($data, '', '&');
			} else {
				$request_body = $data;
			}

			// Always include Content-length on POST requests to prevent
			// 411 errors from some servers when the body is empty.
			if (!empty($data) || $options['type'] === Requests::POST) {
				if (!isset($case_insensitive_headers['Content-Length'])) {
					$headers['Content-Length'] = strlen($request_body);
				}

				if (!isset($case_insensitive_headers['Content-Type'])) {
					$headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
				}
			}
		}

		if (!isset($case_insensitive_headers['Host'])) {
			$out         .= sprintf('Host: %s', $url_parts['host']);
			$scheme_lower = strtolower($url_parts['scheme']);

			if (($scheme_lower === 'http' && $url_parts['port'] !== Port::HTTP) || ($scheme_lower === 'https' && $url_parts['port'] !== Port::HTTPS)) {
				$out .= ':' . $url_parts['port'];
			}

			$out .= "\r\n";
		}

		if (!isset($case_insensitive_headers['User-Agent'])) {
			$out .= sprintf("User-Agent: %s\r\n", $options['useragent']);
		}

		$accept_encoding = $this->accept_encoding();
		if (!isset($case_insensitive_headers['Accept-Encoding']) && !empty($accept_encoding)) {
			$out .= sprintf("Accept-Encoding: %s\r\n", $accept_encoding);
		}

		$headers = Requests::flatten($headers);

		if (!empty($headers)) {
			$out .= implode("\r\n", $headers) . "\r\n";
		}

		$options['hooks']->dispatch('fsockopen.after_headers', [&$out]);

		if (substr($out, -2) !== "\r\n") {
			$out .= "\r\n";
		}

		if (!isset($case_insensitive_headers['Connection'])) {
			$out .= "Connection: Close\r\n";
		}

		$out .= "\r\n" . $request_body;

		$options['hooks']->dispatch('fsockopen.before_send', [&$out]);

		fwrite($socket, $out);
		$options['hooks']->dispatch('fsockopen.after_send', [$out]);

		if (!$options['blocking']) {
			fclose($socket);
			$fake_headers = '';
			$options['hooks']->dispatch('fsockopen.after_request', [&$fake_headers]);
			return '';
		}

		$timeout_sec = (int) floor($options['timeout']);
		if ($timeout_sec === $options['timeout']) {
			$timeout_msec = 0;
		} else {
			$timeout_msec = self::SECOND_IN_MICROSECONDS * $options['timeout'] % self::SECOND_IN_MICROSECONDS;
		}

		stream_set_timeout($socket, $timeout_sec, $timeout_msec);

		$response   = '';
		$body       = '';
		$headers    = '';
		$this->info = stream_get_meta_data($socket);
		$size       = 0;
		$doingbody  = false;
		$download   = false;
		if ($options['filename']) {
			// phpcs:ignore WordPress.PHP.NoSilencedErrors -- Silenced the PHP native warning in favour of throwing an exception.
			$download = @fopen($options['filename'], 'wb');
			if ($download === false) {
				$error = error_get_last();
				throw new Exception($error['message'], 'fopen');
			}
		}

		while (!feof($socket)) {
			$this->info = stream_get_meta_data($socket);
			if ($this->info['timed_out']) {
				throw new Exception('fsocket timed out', 'timeout');
			}

			$block = fread($socket, Requests::BUFFER_SIZE);
			if (!$doingbody) {
				$response .= $block;
				if (strpos($response, "\r\n\r\n")) {
					list($headers, $block) = explode("\r\n\r\n", $response, 2);
					$doingbody             = true;
				}
			}

			// Are we in body mode now?
			if ($doingbody) {
				$options['hooks']->dispatch('request.progress', [$block, $size, $this->max_bytes]);
				$data_length = strlen($block);
				if ($this->max_bytes) {
					// Have we already hit a limit?
					if ($size === $this->max_bytes) {
						continue;
					}

					if (($size + $data_length) > $this->max_bytes) {
						// Limit the length
						$limited_length = ($this->max_bytes - $size);
						$block          = substr($block, 0, $limited_length);
					}
				}

				$size += strlen($block);
				if ($download) {
					fwrite($download, $block);
				} else {
					$body .= $block;
				}
			}
		}

		$this->headers = $headers;

		if ($download) {
			fclose($download);
		} else {
			$this->headers .= "\r\n\r\n" . $body;
		}

		fclose($socket);

		$options['hooks']->dispatch('fsockopen.after_request', [&$this->headers, &$this->info]);
		return $this->headers;
	}

	/**
	 * Send multiple requests simultaneously
	 *
	 * @param array $requests Request data (array of 'url', 'headers', 'data', 'options') as per {@see \WpOrg\Requests\Transport::request()}
	 * @param array $options Global options, see {@see \WpOrg\Requests\Requests::response()} for documentation
	 * @return array Array of \WpOrg\Requests\Response objects (may contain \WpOrg\Requests\Exception or string responses as well)
	 *
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $requests argument is not an array or iterable object with array access.
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array.
	 */
	public function request_multiple($requests, $options) {
		// If you're not requesting, we can't get any responses ¯\_(ツ)_/¯
		if (empty($requests)) {
			return [];
		}

		if (InputValidator::has_array_access($requests) === false || InputValidator::is_iterable($requests) === false) {
			throw InvalidArgument::create(1, '$requests', 'array|ArrayAccess&Traversable', gettype($requests));
		}

		if (is_array($options) === false) {
			throw InvalidArgument::create(2, '$options', 'array', gettype($options));
		}

		$responses = [];
		$class     = get_class($this);
		foreach ($requests as $id => $request) {
			try {
				$handler        = new $class();
				$responses[$id] = $handler->request($request['url'], $request['headers'], $request['data'], $request['options']);

				$request['options']['hooks']->dispatch('transport.internal.parse_response', [&$responses[$id], $request]);
			} catch (Exception $e) {
				$responses[$id] = $e;
			}

			if (!is_string($responses[$id])) {
				$request['options']['hooks']->dispatch('multiple.request.complete', [&$responses[$id], $id]);
			}
		}

		return $responses;
	}

	/**
	 * Retrieve the encodings we can accept
	 *
	 * @return string Accept-Encoding header value
	 */
	private static function accept_encoding() {
		$type = [];
		if (function_exists('gzinflate')) {
			$type[] = 'deflate;q=1.0';
		}

		if (function_exists('gzuncompress')) {
			$type[] = 'compress;q=0.5';
		}

		$type[] = 'gzip;q=0.5';

		return implode(', ', $type);
	}

	/**
	 * Format a URL given GET data
	 *
	 * @param array        $url_parts Array of URL parts as received from {@link https://www.php.net/parse_url}
	 * @param array|object $data Data to build query using, see {@link https://www.php.net/http_build_query}
	 * @return string URL with data
	 */
	private static function format_get($url_parts, $data) {
		if (!empty($data)) {
			if (empty($url_parts['query'])) {
				$url_parts['query'] = '';
			}

			$url_parts['query'] .= '&' . http_build_query($data, '', '&');
			$url_parts['query']  = trim($url_parts['query'], '&');
		}

		if (isset($url_parts['path'])) {
			if (isset($url_parts['query'])) {
				$get = $url_parts['path'] . '?' . $url_parts['query'];
			} else {
				$get = $url_parts['path'];
			}
		} else {
			$get = '/';
		}

		return $get;
	}

	/**
	 * Error handler for stream_socket_client()
	 *
	 * @param int $errno Error number (e.g. E_WARNING)
	 * @param string $errstr Error message
	 */
	public function connect_error_handler($errno, $errstr) {
		// Double-check we can handle it
		if (($errno & E_WARNING) === 0 && ($errno & E_NOTICE) === 0) {
			// Return false to indicate the default error handler should engage
			return false;
		}

		$this->connect_error .= $errstr . "\n";
		return true;
	}

	/**
	 * Verify the certificate against common name and subject alternative names
	 *
	 * Unfortunately, PHP doesn't check the certificate against the alternative
	 * names, leading things like 'https://www.github.com/' to be invalid.
	 * Instead
	 *
	 * @link https://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1
	 *
	 * @param string $host Host name to verify against
	 * @param resource $context Stream context
	 * @return bool
	 *
	 * @throws \WpOrg\Requests\Exception On failure to connect via TLS (`fsockopen.ssl.connect_error`)
	 * @throws \WpOrg\Requests\Exception On not obtaining a match for the host (`fsockopen.ssl.no_match`)
	 */
	public function verify_certificate_from_context($host, $context) {
		$meta = stream_context_get_options($context);

		// If we don't have SSL options, then we couldn't make the connection at
		// all
		if (empty($meta) || empty($meta['ssl']) || empty($meta['ssl']['peer_certificate'])) {
			throw new Exception(rtrim($this->connect_error), 'ssl.connect_error');
		}

		$cert = openssl_x509_parse($meta['ssl']['peer_certificate']);

		return Ssl::verify_certificate($host, $cert);
	}

	/**
	 * Self-test whether the transport can be used.
	 *
	 * The available capabilities to test for can be found in {@see \WpOrg\Requests\Capability}.
	 *
	 * @codeCoverageIgnore
	 * @param array<string, bool> $capabilities Optional. Associative array of capabilities to test against, i.e. `['<capability>' => true]`.
	 * @return bool Whether the transport can be used.
	 */
	public static function test($capabilities = []) {
		if (!function_exists('fsockopen')) {
			return false;
		}

		// If needed, check that streams support SSL
		if (isset($capabilities[Capability::SSL]) && $capabilities[Capability::SSL]) {
			if (!extension_loaded('openssl') || !function_exists('openssl_x509_parse')) {
				return false;
			}
		}

		return true;
	}
}
PK��]ޣ@��Capability.phpnu�[���<?php
/**
 * Capability interface declaring the known capabilities.
 *
 * @package Requests\Utilities
 */

namespace WpOrg\Requests;

/**
 * Capability interface declaring the known capabilities.
 *
 * This is used as the authoritative source for which capabilities can be queried.
 *
 * @package Requests\Utilities
 */
interface Capability {

	/**
	 * Support for SSL.
	 *
	 * @var string
	 */
	const SSL = 'ssl';

	/**
	 * Collection of all capabilities supported in Requests.
	 *
	 * Note: this does not automatically mean that the capability will be supported for your chosen transport!
	 *
	 * @var string[]
	 */
	const ALL = [
		self::SSL,
	];
}
PK��]84�Cookie/Jar.phpnu�[���<?php
/**
 * Cookie holder object
 *
 * @package Requests\Cookies
 */

namespace WpOrg\Requests\Cookie;

use ArrayAccess;
use ArrayIterator;
use IteratorAggregate;
use ReturnTypeWillChange;
use WpOrg\Requests\Cookie;
use WpOrg\Requests\Exception;
use WpOrg\Requests\Exception\InvalidArgument;
use WpOrg\Requests\HookManager;
use WpOrg\Requests\Iri;
use WpOrg\Requests\Response;

/**
 * Cookie holder object
 *
 * @package Requests\Cookies
 */
class Jar implements ArrayAccess, IteratorAggregate {
	/**
	 * Actual item data
	 *
	 * @var array
	 */
	protected $cookies = [];

	/**
	 * Create a new jar
	 *
	 * @param array $cookies Existing cookie values
	 *
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not an array.
	 */
	public function __construct($cookies = []) {
		if (is_array($cookies) === false) {
			throw InvalidArgument::create(1, '$cookies', 'array', gettype($cookies));
		}

		$this->cookies = $cookies;
	}

	/**
	 * Normalise cookie data into a \WpOrg\Requests\Cookie
	 *
	 * @param string|\WpOrg\Requests\Cookie $cookie Cookie header value, possibly pre-parsed (object).
	 * @param string                        $key    Optional. The name for this cookie.
	 * @return \WpOrg\Requests\Cookie
	 */
	public function normalize_cookie($cookie, $key = '') {
		if ($cookie instanceof Cookie) {
			return $cookie;
		}

		return Cookie::parse($cookie, $key);
	}

	/**
	 * Check if the given item exists
	 *
	 * @param string $offset Item key
	 * @return boolean Does the item exist?
	 */
	#[ReturnTypeWillChange]
	public function offsetExists($offset) {
		return isset($this->cookies[$offset]);
	}

	/**
	 * Get the value for the item
	 *
	 * @param string $offset Item key
	 * @return string|null Item value (null if offsetExists is false)
	 */
	#[ReturnTypeWillChange]
	public function offsetGet($offset) {
		if (!isset($this->cookies[$offset])) {
			return null;
		}

		return $this->cookies[$offset];
	}

	/**
	 * Set the given item
	 *
	 * @param string $offset Item name
	 * @param string $value Item value
	 *
	 * @throws \WpOrg\Requests\Exception On attempting to use dictionary as list (`invalidset`)
	 */
	#[ReturnTypeWillChange]
	public function offsetSet($offset, $value) {
		if ($offset === null) {
			throw new Exception('Object is a dictionary, not a list', 'invalidset');
		}

		$this->cookies[$offset] = $value;
	}

	/**
	 * Unset the given header
	 *
	 * @param string $offset The key for the item to unset.
	 */
	#[ReturnTypeWillChange]
	public function offsetUnset($offset) {
		unset($this->cookies[$offset]);
	}

	/**
	 * Get an iterator for the data
	 *
	 * @return \ArrayIterator
	 */
	#[ReturnTypeWillChange]
	public function getIterator() {
		return new ArrayIterator($this->cookies);
	}

	/**
	 * Register the cookie handler with the request's hooking system
	 *
	 * @param \WpOrg\Requests\HookManager $hooks Hooking system
	 */
	public function register(HookManager $hooks) {
		$hooks->register('requests.before_request', [$this, 'before_request']);
		$hooks->register('requests.before_redirect_check', [$this, 'before_redirect_check']);
	}

	/**
	 * Add Cookie header to a request if we have any
	 *
	 * As per RFC 6265, cookies are separated by '; '
	 *
	 * @param string $url
	 * @param array $headers
	 * @param array $data
	 * @param string $type
	 * @param array $options
	 */
	public function before_request($url, &$headers, &$data, &$type, &$options) {
		if (!$url instanceof Iri) {
			$url = new Iri($url);
		}

		if (!empty($this->cookies)) {
			$cookies = [];
			foreach ($this->cookies as $key => $cookie) {
				$cookie = $this->normalize_cookie($cookie, $key);

				// Skip expired cookies
				if ($cookie->is_expired()) {
					continue;
				}

				if ($cookie->domain_matches($url->host)) {
					$cookies[] = $cookie->format_for_header();
				}
			}

			$headers['Cookie'] = implode('; ', $cookies);
		}
	}

	/**
	 * Parse all cookies from a response and attach them to the response
	 *
	 * @param \WpOrg\Requests\Response $response Response as received.
	 */
	public function before_redirect_check(Response $response) {
		$url = $response->url;
		if (!$url instanceof Iri) {
			$url = new Iri($url);
		}

		$cookies           = Cookie::parse_from_headers($response->headers, $url);
		$this->cookies     = array_merge($this->cookies, $cookies);
		$response->cookies = $this;
	}
}
PK��],��S
Transport.phpnu�[���<?php
/**
 * Base HTTP transport
 *
 * @package Requests\Transport
 */

namespace WpOrg\Requests;

/**
 * Base HTTP transport
 *
 * @package Requests\Transport
 */
interface Transport {
	/**
	 * Perform a request
	 *
	 * @param string $url URL to request
	 * @param array $headers Associative array of request headers
	 * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
	 * @param array $options Request options, see {@see \WpOrg\Requests\Requests::response()} for documentation
	 * @return string Raw HTTP result
	 */
	public function request($url, $headers = [], $data = [], $options = []);

	/**
	 * Send multiple requests simultaneously
	 *
	 * @param array $requests Request data (array of 'url', 'headers', 'data', 'options') as per {@see \WpOrg\Requests\Transport::request()}
	 * @param array $options Global options, see {@see \WpOrg\Requests\Requests::response()} for documentation
	 * @return array Array of \WpOrg\Requests\Response objects (may contain \WpOrg\Requests\Exception or string responses as well)
	 */
	public function request_multiple($requests, $options);

	/**
	 * Self-test whether the transport can be used.
	 *
	 * The available capabilities to test for can be found in {@see \WpOrg\Requests\Capability}.
	 *
	 * @param array<string, bool> $capabilities Optional. Associative array of capabilities to test against, i.e. `['<capability>' => true]`.
	 * @return bool Whether the transport can be used.
	 */
	public static function test($capabilities = []);
}
PK��]*���uuException/Transport/Curl.phpnu�[���<?php
/**
 * CURL Transport Exception.
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Transport;

use WpOrg\Requests\Exception\Transport;

/**
 * CURL Transport Exception.
 *
 * @package Requests\Exceptions
 */
final class Curl extends Transport {

	const EASY  = 'cURLEasy';
	const MULTI = 'cURLMulti';
	const SHARE = 'cURLShare';

	/**
	 * cURL error code
	 *
	 * @var integer
	 */
	protected $code = -1;

	/**
	 * Which type of cURL error
	 *
	 * EASY|MULTI|SHARE
	 *
	 * @var string
	 */
	protected $type = 'Unknown';

	/**
	 * Clear text error message
	 *
	 * @var string
	 */
	protected $reason = 'Unknown';

	/**
	 * Create a new exception.
	 *
	 * @param string $message Exception message.
	 * @param string $type    Exception type.
	 * @param mixed  $data    Associated data, if applicable.
	 * @param int    $code    Exception numerical code, if applicable.
	 */
	public function __construct($message, $type, $data = null, $code = 0) {
		if ($type !== null) {
			$this->type = $type;
		}

		if ($code !== null) {
			$this->code = (int) $code;
		}

		if ($message !== null) {
			$this->reason = $message;
		}

		$message = sprintf('%d %s', $this->code, $this->reason);
		parent::__construct($message, $this->type, $data, $this->code);
	}

	/**
	 * Get the error message.
	 *
	 * @return string
	 */
	public function getReason() {
		return $this->reason;
	}

}
PK��]iA���Exception/Transport.phpnu�[���<?php
/**
 * Transport Exception
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception;

use WpOrg\Requests\Exception;

/**
 * Transport Exception
 *
 * @package Requests\Exceptions
 */
class Transport extends Exception {}
PK��]�q�1��Exception/ArgumentCount.phpnu�[���<?php

namespace WpOrg\Requests\Exception;

use WpOrg\Requests\Exception;

/**
 * Exception for when an incorrect number of arguments are passed to a method.
 *
 * Typically, this exception is used when all arguments for a method are optional,
 * but certain arguments need to be passed together, i.e. a method which can be called
 * with no arguments or with two arguments, but not with one argument.
 *
 * Along the same lines, this exception is also used if a method expects an array
 * with a certain number of elements and the provided number of elements does not comply.
 *
 * @package Requests\Exceptions
 * @since   2.0.0
 */
final class ArgumentCount extends Exception {

	/**
	 * Create a new argument count exception with a standardized text.
	 *
	 * @param string $expected The argument count expected as a phrase.
	 *                         For example: `at least 2 arguments` or `exactly 1 argument`.
	 * @param int    $received The actual argument count received.
	 * @param string $type     Exception type.
	 *
	 * @return \WpOrg\Requests\Exception\ArgumentCount
	 */
	public static function create($expected, $received, $type) {
		// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
		$stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);

		return new self(
			sprintf(
				'%s::%s() expects %s, %d given',
				$stack[1]['class'],
				$stack[1]['function'],
				$expected,
				$received
			),
			$type
		);
	}
}
PK��]w;����Exception/Http/Status503.phpnu�[���<?php
/**
 * Exception for 503 Service Unavailable responses
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 503 Service Unavailable responses
 *
 * @package Requests\Exceptions
 */
final class Status503 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 503;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Service Unavailable';
}
PK��]y����Exception/Http/Status404.phpnu�[���<?php
/**
 * Exception for 404 Not Found responses
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 404 Not Found responses
 *
 * @package Requests\Exceptions
 */
final class Status404 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 404;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Not Found';
}
PK��]4��,,Exception/Http/Status418.phpnu�[���<?php
/**
 * Exception for 418 I'm A Teapot responses
 *
 * @link https://tools.ietf.org/html/rfc2324
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 418 I'm A Teapot responses
 *
 * @link https://tools.ietf.org/html/rfc2324
 *
 * @package Requests\Exceptions
 */
final class Status418 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 418;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = "I'm A Teapot";
}
PK��]�� ��Exception/Http/Status500.phpnu�[���<?php
/**
 * Exception for 500 Internal Server Error responses
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 500 Internal Server Error responses
 *
 * @package Requests\Exceptions
 */
final class Status500 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 500;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Internal Server Error';
}
PK��]f�+��Exception/Http/Status417.phpnu�[���<?php
/**
 * Exception for 417 Expectation Failed responses
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 417 Expectation Failed responses
 *
 * @package Requests\Exceptions
 */
final class Status417 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 417;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Expectation Failed';
}
PK��]h$a��Exception/Http/Status306.phpnu�[���<?php
/**
 * Exception for 306 Switch Proxy responses
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 306 Switch Proxy responses
 *
 * @package Requests\Exceptions
 */
final class Status306 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 306;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Switch Proxy';
}
PK��]��(�ssException/Http/Status429.phpnu�[���<?php
/**
 * Exception for 429 Too Many Requests responses
 *
 * @link https://tools.ietf.org/html/draft-nottingham-http-new-status-04
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 429 Too Many Requests responses
 *
 * @link https://tools.ietf.org/html/draft-nottingham-http-new-status-04
 *
 * @package Requests\Exceptions
 */
final class Status429 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 429;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Too Many Requests';
}
PK��]����Exception/Http/Status401.phpnu�[���<?php
/**
 * Exception for 401 Unauthorized responses
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 401 Unauthorized responses
 *
 * @package Requests\Exceptions
 */
final class Status401 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 401;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Unauthorized';
}
PK��]��[F��Exception/Http/Status411.phpnu�[���<?php
/**
 * Exception for 411 Length Required responses
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 411 Length Required responses
 *
 * @package Requests\Exceptions
 */
final class Status411 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 411;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Length Required';
}
PK��]wא��Exception/Http/Status405.phpnu�[���<?php
/**
 * Exception for 405 Method Not Allowed responses
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 405 Method Not Allowed responses
 *
 * @package Requests\Exceptions
 */
final class Status405 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 405;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Method Not Allowed';
}
PK��]k�T���Exception/Http/Status408.phpnu�[���<?php
/**
 * Exception for 408 Request Timeout responses
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 408 Request Timeout responses
 *
 * @package Requests\Exceptions
 */
final class Status408 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 408;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Request Timeout';
}
PK��]Sb���Exception/Http/Status406.phpnu�[���<?php
/**
 * Exception for 406 Not Acceptable responses
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 406 Not Acceptable responses
 *
 * @package Requests\Exceptions
 */
final class Status406 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 406;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Not Acceptable';
}
PK��]m�����Exception/Http/Status505.phpnu�[���<?php
/**
 * Exception for 505 HTTP Version Not Supported responses
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 505 HTTP Version Not Supported responses
 *
 * @package Requests\Exceptions
 */
final class Status505 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 505;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'HTTP Version Not Supported';
}
PK��]h��m��Exception/Http/Status413.phpnu�[���<?php
/**
 * Exception for 413 Request Entity Too Large responses
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 413 Request Entity Too Large responses
 *
 * @package Requests\Exceptions
 */
final class Status413 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 413;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Request Entity Too Large';
}
PK��]&X��eeException/Http/Status431.phpnu�[���<?php
/**
 * Exception for 431 Request Header Fields Too Large responses
 *
 * @link https://tools.ietf.org/html/rfc6585
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 431 Request Header Fields Too Large responses
 *
 * @link https://tools.ietf.org/html/rfc6585
 *
 * @package Requests\Exceptions
 */
final class Status431 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 431;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Request Header Fields Too Large';
}
PK��]{�����Exception/Http/Status402.phpnu�[���<?php
/**
 * Exception for 402 Payment Required responses
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 402 Payment Required responses
 *
 * @package Requests\Exceptions
 */
final class Status402 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 402;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Payment Required';
}
PK��]^��:GGException/Http/Status428.phpnu�[���<?php
/**
 * Exception for 428 Precondition Required responses
 *
 * @link https://tools.ietf.org/html/rfc6585
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 428 Precondition Required responses
 *
 * @link https://tools.ietf.org/html/rfc6585
 *
 * @package Requests\Exceptions
 */
final class Status428 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 428;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Precondition Required';
}
PK��]��P��Exception/Http/Status415.phpnu�[���<?php
/**
 * Exception for 415 Unsupported Media Type responses
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 415 Unsupported Media Type responses
 *
 * @package Requests\Exceptions
 */
final class Status415 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 415;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Unsupported Media Type';
}
PK��]����Exception/Http/Status304.phpnu�[���<?php
/**
 * Exception for 304 Not Modified responses
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 304 Not Modified responses
 *
 * @package Requests\Exceptions
 */
final class Status304 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 304;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Not Modified';
}
PK��]�����Exception/Http/Status403.phpnu�[���<?php
/**
 * Exception for 403 Forbidden responses
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 403 Forbidden responses
 *
 * @package Requests\Exceptions
 */
final class Status403 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 403;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Forbidden';
}
PK��]`���Exception/Http/Status412.phpnu�[���<?php
/**
 * Exception for 412 Precondition Failed responses
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 412 Precondition Failed responses
 *
 * @package Requests\Exceptions
 */
final class Status412 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 412;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Precondition Failed';
}
PK��]mk����Exception/Http/Status501.phpnu�[���<?php
/**
 * Exception for 501 Not Implemented responses
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 501 Not Implemented responses
 *
 * @package Requests\Exceptions
 */
final class Status501 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 501;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Not Implemented';
}
PK��]���*�� Exception/Http/StatusUnknown.phpnu�[���<?php
/**
 * Exception for unknown status responses
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;
use WpOrg\Requests\Response;

/**
 * Exception for unknown status responses
 *
 * @package Requests\Exceptions
 */
final class StatusUnknown extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer|bool Code if available, false if an error occurred
	 */
	protected $code = 0;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Unknown';

	/**
	 * Create a new exception
	 *
	 * If `$data` is an instance of {@see \WpOrg\Requests\Response}, uses the status
	 * code from it. Otherwise, sets as 0
	 *
	 * @param string|null $reason Reason phrase
	 * @param mixed $data Associated data
	 */
	public function __construct($reason = null, $data = null) {
		if ($data instanceof Response) {
			$this->code = (int) $data->status_code;
		}

		parent::__construct($reason, $data);
	}
}
PK��]���Exception/Http/Status416.phpnu�[���<?php
/**
 * Exception for 416 Requested Range Not Satisfiable responses
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 416 Requested Range Not Satisfiable responses
 *
 * @package Requests\Exceptions
 */
final class Status416 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 416;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Requested Range Not Satisfiable';
}
PK��]����eeException/Http/Status511.phpnu�[���<?php
/**
 * Exception for 511 Network Authentication Required responses
 *
 * @link https://tools.ietf.org/html/rfc6585
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 511 Network Authentication Required responses
 *
 * @link https://tools.ietf.org/html/rfc6585
 *
 * @package Requests\Exceptions
 */
final class Status511 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 511;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Network Authentication Required';
}
PK��]�����Exception/Http/Status409.phpnu�[���<?php
/**
 * Exception for 409 Conflict responses
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 409 Conflict responses
 *
 * @package Requests\Exceptions
 */
final class Status409 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 409;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Conflict';
}
PK��]�����Exception/Http/Status305.phpnu�[���<?php
/**
 * Exception for 305 Use Proxy responses
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 305 Use Proxy responses
 *
 * @package Requests\Exceptions
 */
final class Status305 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 305;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Use Proxy';
}
PK��]k�f��Exception/Http/Status504.phpnu�[���<?php
/**
 * Exception for 504 Gateway Timeout responses
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 504 Gateway Timeout responses
 *
 * @package Requests\Exceptions
 */
final class Status504 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 504;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Gateway Timeout';
}
PK��]2d����Exception/Http/Status414.phpnu�[���<?php
/**
 * Exception for 414 Request-URI Too Large responses
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 414 Request-URI Too Large responses
 *
 * @package Requests\Exceptions
 */
final class Status414 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 414;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Request-URI Too Large';
}
PK��]η.p��Exception/Http/Status410.phpnu�[���<?php
/**
 * Exception for 410 Gone responses
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 410 Gone responses
 *
 * @package Requests\Exceptions
 */
final class Status410 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 410;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Gone';
}
PK��]ǿH7��Exception/Http/Status400.phpnu�[���<?php
/**
 * Exception for 400 Bad Request responses
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 400 Bad Request responses
 *
 * @package Requests\Exceptions
 */
final class Status400 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 400;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Bad Request';
}
PK��]]����Exception/Http/Status407.phpnu�[���<?php
/**
 * Exception for 407 Proxy Authentication Required responses
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 407 Proxy Authentication Required responses
 *
 * @package Requests\Exceptions
 */
final class Status407 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 407;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Proxy Authentication Required';
}
PK��]�����Exception/Http/Status502.phpnu�[���<?php
/**
 * Exception for 502 Bad Gateway responses
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception\Http;

use WpOrg\Requests\Exception\Http;

/**
 * Exception for 502 Bad Gateway responses
 *
 * @package Requests\Exceptions
 */
final class Status502 extends Http {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 502;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Bad Gateway';
}
PK��]&��Exception/Http.phpnu�[���<?php
/**
 * Exception based on HTTP response
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests\Exception;

use WpOrg\Requests\Exception;
use WpOrg\Requests\Exception\Http\StatusUnknown;

/**
 * Exception based on HTTP response
 *
 * @package Requests\Exceptions
 */
class Http extends Exception {
	/**
	 * HTTP status code
	 *
	 * @var integer
	 */
	protected $code = 0;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	protected $reason = 'Unknown';

	/**
	 * Create a new exception
	 *
	 * There is no mechanism to pass in the status code, as this is set by the
	 * subclass used. Reason phrases can vary, however.
	 *
	 * @param string|null $reason Reason phrase
	 * @param mixed $data Associated data
	 */
	public function __construct($reason = null, $data = null) {
		if ($reason !== null) {
			$this->reason = $reason;
		}

		$message = sprintf('%d %s', $this->code, $this->reason);
		parent::__construct($message, 'httpresponse', $data, $this->code);
	}

	/**
	 * Get the status message.
	 *
	 * @return string
	 */
	public function getReason() {
		return $this->reason;
	}

	/**
	 * Get the correct exception class for a given error code
	 *
	 * @param int|bool $code HTTP status code, or false if unavailable
	 * @return string Exception class name to use
	 */
	public static function get_class($code) {
		if (!$code) {
			return StatusUnknown::class;
		}

		$class = sprintf('\WpOrg\Requests\Exception\Http\Status%d', $code);
		if (class_exists($class)) {
			return $class;
		}

		return StatusUnknown::class;
	}
}
PK��]���RRException/InvalidArgument.phpnu�[���<?php

namespace WpOrg\Requests\Exception;

use InvalidArgumentException;

/**
 * Exception for an invalid argument passed.
 *
 * @package Requests\Exceptions
 * @since   2.0.0
 */
final class InvalidArgument extends InvalidArgumentException {

	/**
	 * Create a new invalid argument exception with a standardized text.
	 *
	 * @param int    $position The argument position in the function signature. 1-based.
	 * @param string $name     The argument name in the function signature.
	 * @param string $expected The argument type expected as a string.
	 * @param string $received The actual argument type received.
	 *
	 * @return \WpOrg\Requests\Exception\InvalidArgument
	 */
	public static function create($position, $name, $expected, $received) {
		// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_debug_backtrace
		$stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);

		return new self(
			sprintf(
				'%s::%s(): Argument #%d (%s) must be of type %s, %s given',
				$stack[1]['class'],
				$stack[1]['function'],
				$position,
				$name,
				$expected,
				$received
			)
		);
	}
}
PK��]�g�^yyProxy/Http.phpnu�[���<?php
/**
 * HTTP Proxy connection interface
 *
 * @package Requests\Proxy
 * @since   1.6
 */

namespace WpOrg\Requests\Proxy;

use WpOrg\Requests\Exception\ArgumentCount;
use WpOrg\Requests\Exception\InvalidArgument;
use WpOrg\Requests\Hooks;
use WpOrg\Requests\Proxy;

/**
 * HTTP Proxy connection interface
 *
 * Provides a handler for connection via an HTTP proxy
 *
 * @package Requests\Proxy
 * @since   1.6
 */
final class Http implements Proxy {
	/**
	 * Proxy host and port
	 *
	 * Notation: "host:port" (eg 127.0.0.1:8080 or someproxy.com:3128)
	 *
	 * @var string
	 */
	public $proxy;

	/**
	 * Username
	 *
	 * @var string
	 */
	public $user;

	/**
	 * Password
	 *
	 * @var string
	 */
	public $pass;

	/**
	 * Do we need to authenticate? (ie username & password have been provided)
	 *
	 * @var boolean
	 */
	public $use_authentication;

	/**
	 * Constructor
	 *
	 * @since 1.6
	 *
	 * @param array|string|null $args Proxy as a string or an array of proxy, user and password.
	 *                                When passed as an array, must have exactly one (proxy)
	 *                                or three elements (proxy, user, password).
	 *
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not an array, a string or null.
	 * @throws \WpOrg\Requests\Exception\ArgumentCount On incorrect number of arguments (`proxyhttpbadargs`)
	 */
	public function __construct($args = null) {
		if (is_string($args)) {
			$this->proxy = $args;
		} elseif (is_array($args)) {
			if (count($args) === 1) {
				list($this->proxy) = $args;
			} elseif (count($args) === 3) {
				list($this->proxy, $this->user, $this->pass) = $args;
				$this->use_authentication                    = true;
			} else {
				throw ArgumentCount::create(
					'an array with exactly one element or exactly three elements',
					count($args),
					'proxyhttpbadargs'
				);
			}
		} elseif ($args !== null) {
			throw InvalidArgument::create(1, '$args', 'array|string|null', gettype($args));
		}
	}

	/**
	 * Register the necessary callbacks
	 *
	 * @since 1.6
	 * @see \WpOrg\Requests\Proxy\Http::curl_before_send()
	 * @see \WpOrg\Requests\Proxy\Http::fsockopen_remote_socket()
	 * @see \WpOrg\Requests\Proxy\Http::fsockopen_remote_host_path()
	 * @see \WpOrg\Requests\Proxy\Http::fsockopen_header()
	 * @param \WpOrg\Requests\Hooks $hooks Hook system
	 */
	public function register(Hooks $hooks) {
		$hooks->register('curl.before_send', [$this, 'curl_before_send']);

		$hooks->register('fsockopen.remote_socket', [$this, 'fsockopen_remote_socket']);
		$hooks->register('fsockopen.remote_host_path', [$this, 'fsockopen_remote_host_path']);
		if ($this->use_authentication) {
			$hooks->register('fsockopen.after_headers', [$this, 'fsockopen_header']);
		}
	}

	/**
	 * Set cURL parameters before the data is sent
	 *
	 * @since 1.6
	 * @param resource|\CurlHandle $handle cURL handle
	 */
	public function curl_before_send(&$handle) {
		curl_setopt($handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
		curl_setopt($handle, CURLOPT_PROXY, $this->proxy);

		if ($this->use_authentication) {
			curl_setopt($handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
			curl_setopt($handle, CURLOPT_PROXYUSERPWD, $this->get_auth_string());
		}
	}

	/**
	 * Alter remote socket information before opening socket connection
	 *
	 * @since 1.6
	 * @param string $remote_socket Socket connection string
	 */
	public function fsockopen_remote_socket(&$remote_socket) {
		$remote_socket = $this->proxy;
	}

	/**
	 * Alter remote path before getting stream data
	 *
	 * @since 1.6
	 * @param string $path Path to send in HTTP request string ("GET ...")
	 * @param string $url Full URL we're requesting
	 */
	public function fsockopen_remote_host_path(&$path, $url) {
		$path = $url;
	}

	/**
	 * Add extra headers to the request before sending
	 *
	 * @since 1.6
	 * @param string $out HTTP header string
	 */
	public function fsockopen_header(&$out) {
		$out .= sprintf("Proxy-Authorization: Basic %s\r\n", base64_encode($this->get_auth_string()));
	}

	/**
	 * Get the authentication string (user:pass)
	 *
	 * @since 1.6
	 * @return string
	 */
	public function get_auth_string() {
		return $this->user . ':' . $this->pass;
	}
}
PK��]��rw$w$Autoload.phpnu�[���<?php
/**
 * Autoloader for Requests for PHP.
 *
 * Include this file if you'd like to avoid having to create your own autoloader.
 *
 * @package Requests
 * @since   2.0.0
 *
 * @codeCoverageIgnore
 */

namespace WpOrg\Requests;

/*
 * Ensure the autoloader is only declared once.
 * This safeguard is in place as this is the typical entry point for this library
 * and this file being required unconditionally could easily cause
 * fatal "Class already declared" errors.
 */
if (class_exists('WpOrg\Requests\Autoload') === false) {

	/**
	 * Autoloader for Requests for PHP.
	 *
	 * This autoloader supports the PSR-4 based Requests 2.0.0 classes in a case-sensitive manner
	 * as the most common server OS-es are case-sensitive and the file names are in mixed case.
	 *
	 * For the PSR-0 Requests 1.x BC-layer, requested classes will be treated case-insensitively.
	 *
	 * @package Requests
	 */
	final class Autoload {

		/**
		 * List of the old PSR-0 class names in lowercase as keys with their PSR-4 case-sensitive name as a value.
		 *
		 * @var array
		 */
		private static $deprecated_classes = [
			// Interfaces.
			'requests_auth'                              => '\WpOrg\Requests\Auth',
			'requests_hooker'                            => '\WpOrg\Requests\HookManager',
			'requests_proxy'                             => '\WpOrg\Requests\Proxy',
			'requests_transport'                         => '\WpOrg\Requests\Transport',

			// Classes.
			'requests_cookie'                            => '\WpOrg\Requests\Cookie',
			'requests_exception'                         => '\WpOrg\Requests\Exception',
			'requests_hooks'                             => '\WpOrg\Requests\Hooks',
			'requests_idnaencoder'                       => '\WpOrg\Requests\IdnaEncoder',
			'requests_ipv6'                              => '\WpOrg\Requests\Ipv6',
			'requests_iri'                               => '\WpOrg\Requests\Iri',
			'requests_response'                          => '\WpOrg\Requests\Response',
			'requests_session'                           => '\WpOrg\Requests\Session',
			'requests_ssl'                               => '\WpOrg\Requests\Ssl',
			'requests_auth_basic'                        => '\WpOrg\Requests\Auth\Basic',
			'requests_cookie_jar'                        => '\WpOrg\Requests\Cookie\Jar',
			'requests_proxy_http'                        => '\WpOrg\Requests\Proxy\Http',
			'requests_response_headers'                  => '\WpOrg\Requests\Response\Headers',
			'requests_transport_curl'                    => '\WpOrg\Requests\Transport\Curl',
			'requests_transport_fsockopen'               => '\WpOrg\Requests\Transport\Fsockopen',
			'requests_utility_caseinsensitivedictionary' => '\WpOrg\Requests\Utility\CaseInsensitiveDictionary',
			'requests_utility_filterediterator'          => '\WpOrg\Requests\Utility\FilteredIterator',
			'requests_exception_http'                    => '\WpOrg\Requests\Exception\Http',
			'requests_exception_transport'               => '\WpOrg\Requests\Exception\Transport',
			'requests_exception_transport_curl'          => '\WpOrg\Requests\Exception\Transport\Curl',
			'requests_exception_http_304'                => '\WpOrg\Requests\Exception\Http\Status304',
			'requests_exception_http_305'                => '\WpOrg\Requests\Exception\Http\Status305',
			'requests_exception_http_306'                => '\WpOrg\Requests\Exception\Http\Status306',
			'requests_exception_http_400'                => '\WpOrg\Requests\Exception\Http\Status400',
			'requests_exception_http_401'                => '\WpOrg\Requests\Exception\Http\Status401',
			'requests_exception_http_402'                => '\WpOrg\Requests\Exception\Http\Status402',
			'requests_exception_http_403'                => '\WpOrg\Requests\Exception\Http\Status403',
			'requests_exception_http_404'                => '\WpOrg\Requests\Exception\Http\Status404',
			'requests_exception_http_405'                => '\WpOrg\Requests\Exception\Http\Status405',
			'requests_exception_http_406'                => '\WpOrg\Requests\Exception\Http\Status406',
			'requests_exception_http_407'                => '\WpOrg\Requests\Exception\Http\Status407',
			'requests_exception_http_408'                => '\WpOrg\Requests\Exception\Http\Status408',
			'requests_exception_http_409'                => '\WpOrg\Requests\Exception\Http\Status409',
			'requests_exception_http_410'                => '\WpOrg\Requests\Exception\Http\Status410',
			'requests_exception_http_411'                => '\WpOrg\Requests\Exception\Http\Status411',
			'requests_exception_http_412'                => '\WpOrg\Requests\Exception\Http\Status412',
			'requests_exception_http_413'                => '\WpOrg\Requests\Exception\Http\Status413',
			'requests_exception_http_414'                => '\WpOrg\Requests\Exception\Http\Status414',
			'requests_exception_http_415'                => '\WpOrg\Requests\Exception\Http\Status415',
			'requests_exception_http_416'                => '\WpOrg\Requests\Exception\Http\Status416',
			'requests_exception_http_417'                => '\WpOrg\Requests\Exception\Http\Status417',
			'requests_exception_http_418'                => '\WpOrg\Requests\Exception\Http\Status418',
			'requests_exception_http_428'                => '\WpOrg\Requests\Exception\Http\Status428',
			'requests_exception_http_429'                => '\WpOrg\Requests\Exception\Http\Status429',
			'requests_exception_http_431'                => '\WpOrg\Requests\Exception\Http\Status431',
			'requests_exception_http_500'                => '\WpOrg\Requests\Exception\Http\Status500',
			'requests_exception_http_501'                => '\WpOrg\Requests\Exception\Http\Status501',
			'requests_exception_http_502'                => '\WpOrg\Requests\Exception\Http\Status502',
			'requests_exception_http_503'                => '\WpOrg\Requests\Exception\Http\Status503',
			'requests_exception_http_504'                => '\WpOrg\Requests\Exception\Http\Status504',
			'requests_exception_http_505'                => '\WpOrg\Requests\Exception\Http\Status505',
			'requests_exception_http_511'                => '\WpOrg\Requests\Exception\Http\Status511',
			'requests_exception_http_unknown'            => '\WpOrg\Requests\Exception\Http\StatusUnknown',
		];

		/**
		 * Register the autoloader.
		 *
		 * Note: the autoloader is *prepended* in the autoload queue.
		 * This is done to ensure that the Requests 2.0 autoloader takes precedence
		 * over a potentially (dependency-registered) Requests 1.x autoloader.
		 *
		 * @internal This method contains a safeguard against the autoloader being
		 * registered multiple times. This safeguard uses a global constant to
		 * (hopefully/in most cases) still function correctly, even if the
		 * class would be renamed.
		 *
		 * @return void
		 */
		public static function register() {
			if (defined('REQUESTS_AUTOLOAD_REGISTERED') === false) {
				spl_autoload_register([self::class, 'load'], true);
				define('REQUESTS_AUTOLOAD_REGISTERED', true);
			}
		}

		/**
		 * Autoloader.
		 *
		 * @param string $class_name Name of the class name to load.
		 *
		 * @return bool Whether a class was loaded or not.
		 */
		public static function load($class_name) {
			// Check that the class starts with "Requests" (PSR-0) or "WpOrg\Requests" (PSR-4).
			$psr_4_prefix_pos = strpos($class_name, 'WpOrg\\Requests\\');

			if (stripos($class_name, 'Requests') !== 0 && $psr_4_prefix_pos !== 0) {
				return false;
			}

			$class_lower = strtolower($class_name);

			if ($class_lower === 'requests') {
				// Reference to the original PSR-0 Requests class.
				$file = dirname(__DIR__) . '/library/Requests.php';
			} elseif ($psr_4_prefix_pos === 0) {
				// PSR-4 classname.
				$file = __DIR__ . '/' . strtr(substr($class_name, 15), '\\', '/') . '.php';
			}

			if (isset($file) && file_exists($file)) {
				include $file;
				return true;
			}

			/*
			 * Okay, so the class starts with "Requests", but we couldn't find the file.
			 * If this is one of the deprecated/renamed PSR-0 classes being requested,
			 * let's alias it to the new name and throw a deprecation notice.
			 */
			if (isset(self::$deprecated_classes[$class_lower])) {
				/*
				 * Integrators who cannot yet upgrade to the PSR-4 class names can silence deprecations
				 * by defining a `REQUESTS_SILENCE_PSR0_DEPRECATIONS` constant and setting it to `true`.
				 * The constant needs to be defined before the first deprecated class is requested
				 * via this autoloader.
				 */
				if (!defined('REQUESTS_SILENCE_PSR0_DEPRECATIONS') || REQUESTS_SILENCE_PSR0_DEPRECATIONS !== true) {
					// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
					trigger_error(
						'The PSR-0 `Requests_...` class names in the Requests library are deprecated.'
						. ' Switch to the PSR-4 `WpOrg\Requests\...` class names at your earliest convenience.',
						E_USER_DEPRECATED
					);

					// Prevent the deprecation notice from being thrown twice.
					if (!defined('REQUESTS_SILENCE_PSR0_DEPRECATIONS')) {
						define('REQUESTS_SILENCE_PSR0_DEPRECATIONS', true);
					}
				}

				// Create an alias and let the autoloader recursively kick in to load the PSR-4 class.
				return class_alias(self::$deprecated_classes[$class_lower], $class_name, true);
			}

			return false;
		}
	}
}
PK��]�
��s�sIri.phpnu�[���<?php
/**
 * IRI parser/serialiser/normaliser
 *
 * @package Requests\Utilities
 */

namespace WpOrg\Requests;

use WpOrg\Requests\Exception;
use WpOrg\Requests\Exception\InvalidArgument;
use WpOrg\Requests\Ipv6;
use WpOrg\Requests\Port;
use WpOrg\Requests\Utility\InputValidator;

/**
 * IRI parser/serialiser/normaliser
 *
 * Copyright (c) 2007-2010, Geoffrey Sneddon and Steve Minutillo.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *
 *  * Redistributions in binary form must reproduce the above copyright notice,
 *       this list of conditions and the following disclaimer in the documentation
 *       and/or other materials provided with the distribution.
 *
 *  * Neither the name of the SimplePie Team nor the names of its contributors
 *       may be used to endorse or promote products derived from this software
 *       without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package Requests\Utilities
 * @author Geoffrey Sneddon
 * @author Steve Minutillo
 * @copyright 2007-2009 Geoffrey Sneddon and Steve Minutillo
 * @license https://opensource.org/licenses/bsd-license.php
 * @link http://hg.gsnedders.com/iri/
 *
 * @property string $iri IRI we're working with
 * @property-read string $uri IRI in URI form, {@see \WpOrg\Requests\Iri::to_uri()}
 * @property string $scheme Scheme part of the IRI
 * @property string $authority Authority part, formatted for a URI (userinfo + host + port)
 * @property string $iauthority Authority part of the IRI (userinfo + host + port)
 * @property string $userinfo Userinfo part, formatted for a URI (after '://' and before '@')
 * @property string $iuserinfo Userinfo part of the IRI (after '://' and before '@')
 * @property string $host Host part, formatted for a URI
 * @property string $ihost Host part of the IRI
 * @property string $port Port part of the IRI (after ':')
 * @property string $path Path part, formatted for a URI (after first '/')
 * @property string $ipath Path part of the IRI (after first '/')
 * @property string $query Query part, formatted for a URI (after '?')
 * @property string $iquery Query part of the IRI (after '?')
 * @property string $fragment Fragment, formatted for a URI (after '#')
 * @property string $ifragment Fragment part of the IRI (after '#')
 */
class Iri {
	/**
	 * Scheme
	 *
	 * @var string|null
	 */
	protected $scheme = null;

	/**
	 * User Information
	 *
	 * @var string|null
	 */
	protected $iuserinfo = null;

	/**
	 * ihost
	 *
	 * @var string|null
	 */
	protected $ihost = null;

	/**
	 * Port
	 *
	 * @var string|null
	 */
	protected $port = null;

	/**
	 * ipath
	 *
	 * @var string
	 */
	protected $ipath = '';

	/**
	 * iquery
	 *
	 * @var string|null
	 */
	protected $iquery = null;

	/**
	 * ifragment|null
	 *
	 * @var string
	 */
	protected $ifragment = null;

	/**
	 * Normalization database
	 *
	 * Each key is the scheme, each value is an array with each key as the IRI
	 * part and value as the default value for that part.
	 *
	 * @var array
	 */
	protected $normalization = array(
		'acap' => array(
			'port' => Port::ACAP,
		),
		'dict' => array(
			'port' => Port::DICT,
		),
		'file' => array(
			'ihost' => 'localhost',
		),
		'http' => array(
			'port' => Port::HTTP,
		),
		'https' => array(
			'port' => Port::HTTPS,
		),
	);

	/**
	 * Return the entire IRI when you try and read the object as a string
	 *
	 * @return string
	 */
	public function __toString() {
		return $this->get_iri();
	}

	/**
	 * Overload __set() to provide access via properties
	 *
	 * @param string $name Property name
	 * @param mixed $value Property value
	 */
	public function __set($name, $value) {
		if (method_exists($this, 'set_' . $name)) {
			call_user_func(array($this, 'set_' . $name), $value);
		}
		elseif (
			   $name === 'iauthority'
			|| $name === 'iuserinfo'
			|| $name === 'ihost'
			|| $name === 'ipath'
			|| $name === 'iquery'
			|| $name === 'ifragment'
		) {
			call_user_func(array($this, 'set_' . substr($name, 1)), $value);
		}
	}

	/**
	 * Overload __get() to provide access via properties
	 *
	 * @param string $name Property name
	 * @return mixed
	 */
	public function __get($name) {
		// isset() returns false for null, we don't want to do that
		// Also why we use array_key_exists below instead of isset()
		$props = get_object_vars($this);

		if (
			$name === 'iri' ||
			$name === 'uri' ||
			$name === 'iauthority' ||
			$name === 'authority'
		) {
			$method = 'get_' . $name;
			$return = $this->$method();
		}
		elseif (array_key_exists($name, $props)) {
			$return = $this->$name;
		}
		// host -> ihost
		elseif (($prop = 'i' . $name) && array_key_exists($prop, $props)) {
			$name = $prop;
			$return = $this->$prop;
		}
		// ischeme -> scheme
		elseif (($prop = substr($name, 1)) && array_key_exists($prop, $props)) {
			$name = $prop;
			$return = $this->$prop;
		}
		else {
			trigger_error('Undefined property: ' . get_class($this) . '::' . $name, E_USER_NOTICE);
			$return = null;
		}

		if ($return === null && isset($this->normalization[$this->scheme][$name])) {
			return $this->normalization[$this->scheme][$name];
		}
		else {
			return $return;
		}
	}

	/**
	 * Overload __isset() to provide access via properties
	 *
	 * @param string $name Property name
	 * @return bool
	 */
	public function __isset($name) {
		return (method_exists($this, 'get_' . $name) || isset($this->$name));
	}

	/**
	 * Overload __unset() to provide access via properties
	 *
	 * @param string $name Property name
	 */
	public function __unset($name) {
		if (method_exists($this, 'set_' . $name)) {
			call_user_func(array($this, 'set_' . $name), '');
		}
	}

	/**
	 * Create a new IRI object, from a specified string
	 *
	 * @param string|Stringable|null $iri
	 *
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $iri argument is not a string, Stringable or null.
	 */
	public function __construct($iri = null) {
		if ($iri !== null && InputValidator::is_string_or_stringable($iri) === false) {
			throw InvalidArgument::create(1, '$iri', 'string|Stringable|null', gettype($iri));
		}

		$this->set_iri($iri);
	}

	/**
	 * Create a new IRI object by resolving a relative IRI
	 *
	 * Returns false if $base is not absolute, otherwise an IRI.
	 *
	 * @param \WpOrg\Requests\Iri|string $base (Absolute) Base IRI
	 * @param \WpOrg\Requests\Iri|string $relative Relative IRI
	 * @return \WpOrg\Requests\Iri|false
	 */
	public static function absolutize($base, $relative) {
		if (!($relative instanceof self)) {
			$relative = new self($relative);
		}
		if (!$relative->is_valid()) {
			return false;
		}
		elseif ($relative->scheme !== null) {
			return clone $relative;
		}

		if (!($base instanceof self)) {
			$base = new self($base);
		}
		if ($base->scheme === null || !$base->is_valid()) {
			return false;
		}

		if ($relative->get_iri() !== '') {
			if ($relative->iuserinfo !== null || $relative->ihost !== null || $relative->port !== null) {
				$target = clone $relative;
				$target->scheme = $base->scheme;
			}
			else {
				$target = new self;
				$target->scheme = $base->scheme;
				$target->iuserinfo = $base->iuserinfo;
				$target->ihost = $base->ihost;
				$target->port = $base->port;
				if ($relative->ipath !== '') {
					if ($relative->ipath[0] === '/') {
						$target->ipath = $relative->ipath;
					}
					elseif (($base->iuserinfo !== null || $base->ihost !== null || $base->port !== null) && $base->ipath === '') {
						$target->ipath = '/' . $relative->ipath;
					}
					elseif (($last_segment = strrpos($base->ipath, '/')) !== false) {
						$target->ipath = substr($base->ipath, 0, $last_segment + 1) . $relative->ipath;
					}
					else {
						$target->ipath = $relative->ipath;
					}
					$target->ipath = $target->remove_dot_segments($target->ipath);
					$target->iquery = $relative->iquery;
				}
				else {
					$target->ipath = $base->ipath;
					if ($relative->iquery !== null) {
						$target->iquery = $relative->iquery;
					}
					elseif ($base->iquery !== null) {
						$target->iquery = $base->iquery;
					}
				}
				$target->ifragment = $relative->ifragment;
			}
		}
		else {
			$target = clone $base;
			$target->ifragment = null;
		}
		$target->scheme_normalization();
		return $target;
	}

	/**
	 * Parse an IRI into scheme/authority/path/query/fragment segments
	 *
	 * @param string $iri
	 * @return array
	 */
	protected function parse_iri($iri) {
		$iri = trim($iri, "\x20\x09\x0A\x0C\x0D");
		$has_match = preg_match('/^((?P<scheme>[^:\/?#]+):)?(\/\/(?P<authority>[^\/?#]*))?(?P<path>[^?#]*)(\?(?P<query>[^#]*))?(#(?P<fragment>.*))?$/', $iri, $match);
		if (!$has_match) {
			throw new Exception('Cannot parse supplied IRI', 'iri.cannot_parse', $iri);
		}

		if ($match[1] === '') {
			$match['scheme'] = null;
		}
		if (!isset($match[3]) || $match[3] === '') {
			$match['authority'] = null;
		}
		if (!isset($match[5])) {
			$match['path'] = '';
		}
		if (!isset($match[6]) || $match[6] === '') {
			$match['query'] = null;
		}
		if (!isset($match[8]) || $match[8] === '') {
			$match['fragment'] = null;
		}
		return $match;
	}

	/**
	 * Remove dot segments from a path
	 *
	 * @param string $input
	 * @return string
	 */
	protected function remove_dot_segments($input) {
		$output = '';
		while (strpos($input, './') !== false || strpos($input, '/.') !== false || $input === '.' || $input === '..') {
			// A: If the input buffer begins with a prefix of "../" or "./",
			// then remove that prefix from the input buffer; otherwise,
			if (strpos($input, '../') === 0) {
				$input = substr($input, 3);
			}
			elseif (strpos($input, './') === 0) {
				$input = substr($input, 2);
			}
			// B: if the input buffer begins with a prefix of "/./" or "/.",
			// where "." is a complete path segment, then replace that prefix
			// with "/" in the input buffer; otherwise,
			elseif (strpos($input, '/./') === 0) {
				$input = substr($input, 2);
			}
			elseif ($input === '/.') {
				$input = '/';
			}
			// C: if the input buffer begins with a prefix of "/../" or "/..",
			// where ".." is a complete path segment, then replace that prefix
			// with "/" in the input buffer and remove the last segment and its
			// preceding "/" (if any) from the output buffer; otherwise,
			elseif (strpos($input, '/../') === 0) {
				$input = substr($input, 3);
				$output = substr_replace($output, '', (strrpos($output, '/') ?: 0));
			}
			elseif ($input === '/..') {
				$input = '/';
				$output = substr_replace($output, '', (strrpos($output, '/') ?: 0));
			}
			// D: if the input buffer consists only of "." or "..", then remove
			// that from the input buffer; otherwise,
			elseif ($input === '.' || $input === '..') {
				$input = '';
			}
			// E: move the first path segment in the input buffer to the end of
			// the output buffer, including the initial "/" character (if any)
			// and any subsequent characters up to, but not including, the next
			// "/" character or the end of the input buffer
			elseif (($pos = strpos($input, '/', 1)) !== false) {
				$output .= substr($input, 0, $pos);
				$input = substr_replace($input, '', 0, $pos);
			}
			else {
				$output .= $input;
				$input = '';
			}
		}
		return $output . $input;
	}

	/**
	 * Replace invalid character with percent encoding
	 *
	 * @param string $text Input string
	 * @param string $extra_chars Valid characters not in iunreserved or
	 *                            iprivate (this is ASCII-only)
	 * @param bool $iprivate Allow iprivate
	 * @return string
	 */
	protected function replace_invalid_with_pct_encoding($text, $extra_chars, $iprivate = false) {
		// Normalize as many pct-encoded sections as possible
		$text = preg_replace_callback('/(?:%[A-Fa-f0-9]{2})+/', array($this, 'remove_iunreserved_percent_encoded'), $text);

		// Replace invalid percent characters
		$text = preg_replace('/%(?![A-Fa-f0-9]{2})/', '%25', $text);

		// Add unreserved and % to $extra_chars (the latter is safe because all
		// pct-encoded sections are now valid).
		$extra_chars .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~%';

		// Now replace any bytes that aren't allowed with their pct-encoded versions
		$position = 0;
		$strlen = strlen($text);
		while (($position += strspn($text, $extra_chars, $position)) < $strlen) {
			$value = ord($text[$position]);

			// Start position
			$start = $position;

			// By default we are valid
			$valid = true;

			// No one byte sequences are valid due to the while.
			// Two byte sequence:
			if (($value & 0xE0) === 0xC0) {
				$character = ($value & 0x1F) << 6;
				$length = 2;
				$remaining = 1;
			}
			// Three byte sequence:
			elseif (($value & 0xF0) === 0xE0) {
				$character = ($value & 0x0F) << 12;
				$length = 3;
				$remaining = 2;
			}
			// Four byte sequence:
			elseif (($value & 0xF8) === 0xF0) {
				$character = ($value & 0x07) << 18;
				$length = 4;
				$remaining = 3;
			}
			// Invalid byte:
			else {
				$valid = false;
				$length = 1;
				$remaining = 0;
			}

			if ($remaining) {
				if ($position + $length <= $strlen) {
					for ($position++; $remaining; $position++) {
						$value = ord($text[$position]);

						// Check that the byte is valid, then add it to the character:
						if (($value & 0xC0) === 0x80) {
							$character |= ($value & 0x3F) << (--$remaining * 6);
						}
						// If it is invalid, count the sequence as invalid and reprocess the current byte:
						else {
							$valid = false;
							$position--;
							break;
						}
					}
				}
				else {
					$position = $strlen - 1;
					$valid = false;
				}
			}

			// Percent encode anything invalid or not in ucschar
			if (
				// Invalid sequences
				!$valid
				// Non-shortest form sequences are invalid
				|| $length > 1 && $character <= 0x7F
				|| $length > 2 && $character <= 0x7FF
				|| $length > 3 && $character <= 0xFFFF
				// Outside of range of ucschar codepoints
				// Noncharacters
				|| ($character & 0xFFFE) === 0xFFFE
				|| $character >= 0xFDD0 && $character <= 0xFDEF
				|| (
					// Everything else not in ucschar
					   $character > 0xD7FF && $character < 0xF900
					|| $character < 0xA0
					|| $character > 0xEFFFD
				)
				&& (
					// Everything not in iprivate, if it applies
					   !$iprivate
					|| $character < 0xE000
					|| $character > 0x10FFFD
				)
			) {
				// If we were a character, pretend we weren't, but rather an error.
				if ($valid) {
					$position--;
				}

				for ($j = $start; $j <= $position; $j++) {
					$text = substr_replace($text, sprintf('%%%02X', ord($text[$j])), $j, 1);
					$j += 2;
					$position += 2;
					$strlen += 2;
				}
			}
		}

		return $text;
	}

	/**
	 * Callback function for preg_replace_callback.
	 *
	 * Removes sequences of percent encoded bytes that represent UTF-8
	 * encoded characters in iunreserved
	 *
	 * @param array $regex_match PCRE match
	 * @return string Replacement
	 */
	protected function remove_iunreserved_percent_encoded($regex_match) {
		// As we just have valid percent encoded sequences we can just explode
		// and ignore the first member of the returned array (an empty string).
		$bytes = explode('%', $regex_match[0]);

		// Initialize the new string (this is what will be returned) and that
		// there are no bytes remaining in the current sequence (unsurprising
		// at the first byte!).
		$string = '';
		$remaining = 0;

		// Loop over each and every byte, and set $value to its value
		for ($i = 1, $len = count($bytes); $i < $len; $i++) {
			$value = hexdec($bytes[$i]);

			// If we're the first byte of sequence:
			if (!$remaining) {
				// Start position
				$start = $i;

				// By default we are valid
				$valid = true;

				// One byte sequence:
				if ($value <= 0x7F) {
					$character = $value;
					$length = 1;
				}
				// Two byte sequence:
				elseif (($value & 0xE0) === 0xC0) {
					$character = ($value & 0x1F) << 6;
					$length = 2;
					$remaining = 1;
				}
				// Three byte sequence:
				elseif (($value & 0xF0) === 0xE0) {
					$character = ($value & 0x0F) << 12;
					$length = 3;
					$remaining = 2;
				}
				// Four byte sequence:
				elseif (($value & 0xF8) === 0xF0) {
					$character = ($value & 0x07) << 18;
					$length = 4;
					$remaining = 3;
				}
				// Invalid byte:
				else {
					$valid = false;
					$remaining = 0;
				}
			}
			// Continuation byte:
			else {
				// Check that the byte is valid, then add it to the character:
				if (($value & 0xC0) === 0x80) {
					$remaining--;
					$character |= ($value & 0x3F) << ($remaining * 6);
				}
				// If it is invalid, count the sequence as invalid and reprocess the current byte as the start of a sequence:
				else {
					$valid = false;
					$remaining = 0;
					$i--;
				}
			}

			// If we've reached the end of the current byte sequence, append it to Unicode::$data
			if (!$remaining) {
				// Percent encode anything invalid or not in iunreserved
				if (
					// Invalid sequences
					!$valid
					// Non-shortest form sequences are invalid
					|| $length > 1 && $character <= 0x7F
					|| $length > 2 && $character <= 0x7FF
					|| $length > 3 && $character <= 0xFFFF
					// Outside of range of iunreserved codepoints
					|| $character < 0x2D
					|| $character > 0xEFFFD
					// Noncharacters
					|| ($character & 0xFFFE) === 0xFFFE
					|| $character >= 0xFDD0 && $character <= 0xFDEF
					// Everything else not in iunreserved (this is all BMP)
					|| $character === 0x2F
					|| $character > 0x39 && $character < 0x41
					|| $character > 0x5A && $character < 0x61
					|| $character > 0x7A && $character < 0x7E
					|| $character > 0x7E && $character < 0xA0
					|| $character > 0xD7FF && $character < 0xF900
				) {
					for ($j = $start; $j <= $i; $j++) {
						$string .= '%' . strtoupper($bytes[$j]);
					}
				}
				else {
					for ($j = $start; $j <= $i; $j++) {
						$string .= chr(hexdec($bytes[$j]));
					}
				}
			}
		}

		// If we have any bytes left over they are invalid (i.e., we are
		// mid-way through a multi-byte sequence)
		if ($remaining) {
			for ($j = $start; $j < $len; $j++) {
				$string .= '%' . strtoupper($bytes[$j]);
			}
		}

		return $string;
	}

	protected function scheme_normalization() {
		if (isset($this->normalization[$this->scheme]['iuserinfo']) && $this->iuserinfo === $this->normalization[$this->scheme]['iuserinfo']) {
			$this->iuserinfo = null;
		}
		if (isset($this->normalization[$this->scheme]['ihost']) && $this->ihost === $this->normalization[$this->scheme]['ihost']) {
			$this->ihost = null;
		}
		if (isset($this->normalization[$this->scheme]['port']) && $this->port === $this->normalization[$this->scheme]['port']) {
			$this->port = null;
		}
		if (isset($this->normalization[$this->scheme]['ipath']) && $this->ipath === $this->normalization[$this->scheme]['ipath']) {
			$this->ipath = '';
		}
		if (isset($this->ihost) && empty($this->ipath)) {
			$this->ipath = '/';
		}
		if (isset($this->normalization[$this->scheme]['iquery']) && $this->iquery === $this->normalization[$this->scheme]['iquery']) {
			$this->iquery = null;
		}
		if (isset($this->normalization[$this->scheme]['ifragment']) && $this->ifragment === $this->normalization[$this->scheme]['ifragment']) {
			$this->ifragment = null;
		}
	}

	/**
	 * Check if the object represents a valid IRI. This needs to be done on each
	 * call as some things change depending on another part of the IRI.
	 *
	 * @return bool
	 */
	public function is_valid() {
		$isauthority = $this->iuserinfo !== null || $this->ihost !== null || $this->port !== null;
		if ($this->ipath !== '' &&
			(
				$isauthority && $this->ipath[0] !== '/' ||
				(
					$this->scheme === null &&
					!$isauthority &&
					strpos($this->ipath, ':') !== false &&
					(strpos($this->ipath, '/') === false ? true : strpos($this->ipath, ':') < strpos($this->ipath, '/'))
				)
			)
		) {
			return false;
		}

		return true;
	}

	public function __wakeup() {
		$class_props = get_class_vars( __CLASS__ );
		$string_props = array( 'scheme', 'iuserinfo', 'ihost', 'port', 'ipath', 'iquery', 'ifragment' );
		$array_props = array( 'normalization' );
		foreach ( $class_props as $prop => $default_value ) {
			if ( in_array( $prop, $string_props, true ) && ! is_string( $this->$prop ) ) {
				throw new UnexpectedValueException();
			} elseif ( in_array( $prop, $array_props, true ) && ! is_array( $this->$prop ) ) {
				throw new UnexpectedValueException();
			}
			$this->$prop = null;
		}
	}

	/**
	 * Set the entire IRI. Returns true on success, false on failure (if there
	 * are any invalid characters).
	 *
	 * @param string $iri
	 * @return bool
	 */
	protected function set_iri($iri) {
		static $cache;
		if (!$cache) {
			$cache = array();
		}

		if ($iri === null) {
			return true;
		}

		$iri = (string) $iri;

		if (isset($cache[$iri])) {
			list($this->scheme,
				 $this->iuserinfo,
				 $this->ihost,
				 $this->port,
				 $this->ipath,
				 $this->iquery,
				 $this->ifragment,
				 $return) = $cache[$iri];
			return $return;
		}

		$parsed = $this->parse_iri($iri);

		$return = $this->set_scheme($parsed['scheme'])
			&& $this->set_authority($parsed['authority'])
			&& $this->set_path($parsed['path'])
			&& $this->set_query($parsed['query'])
			&& $this->set_fragment($parsed['fragment']);

		$cache[$iri] = array($this->scheme,
							 $this->iuserinfo,
							 $this->ihost,
							 $this->port,
							 $this->ipath,
							 $this->iquery,
							 $this->ifragment,
							 $return);
		return $return;
	}

	/**
	 * Set the scheme. Returns true on success, false on failure (if there are
	 * any invalid characters).
	 *
	 * @param string $scheme
	 * @return bool
	 */
	protected function set_scheme($scheme) {
		if ($scheme === null) {
			$this->scheme = null;
		}
		elseif (!preg_match('/^[A-Za-z][0-9A-Za-z+\-.]*$/', $scheme)) {
			$this->scheme = null;
			return false;
		}
		else {
			$this->scheme = strtolower($scheme);
		}
		return true;
	}

	/**
	 * Set the authority. Returns true on success, false on failure (if there are
	 * any invalid characters).
	 *
	 * @param string $authority
	 * @return bool
	 */
	protected function set_authority($authority) {
		static $cache;
		if (!$cache) {
			$cache = array();
		}

		if ($authority === null) {
			$this->iuserinfo = null;
			$this->ihost = null;
			$this->port = null;
			return true;
		}
		if (isset($cache[$authority])) {
			list($this->iuserinfo,
				 $this->ihost,
				 $this->port,
				 $return) = $cache[$authority];

			return $return;
		}

		$remaining = $authority;
		if (($iuserinfo_end = strrpos($remaining, '@')) !== false) {
			$iuserinfo = substr($remaining, 0, $iuserinfo_end);
			$remaining = substr($remaining, $iuserinfo_end + 1);
		}
		else {
			$iuserinfo = null;
		}

		if (($port_start = strpos($remaining, ':', (strpos($remaining, ']') ?: 0))) !== false) {
			$port = substr($remaining, $port_start + 1);
			if ($port === false || $port === '') {
				$port = null;
			}
			$remaining = substr($remaining, 0, $port_start);
		}
		else {
			$port = null;
		}

		$return = $this->set_userinfo($iuserinfo) &&
				  $this->set_host($remaining) &&
				  $this->set_port($port);

		$cache[$authority] = array($this->iuserinfo,
								   $this->ihost,
								   $this->port,
								   $return);

		return $return;
	}

	/**
	 * Set the iuserinfo.
	 *
	 * @param string $iuserinfo
	 * @return bool
	 */
	protected function set_userinfo($iuserinfo) {
		if ($iuserinfo === null) {
			$this->iuserinfo = null;
		}
		else {
			$this->iuserinfo = $this->replace_invalid_with_pct_encoding($iuserinfo, '!$&\'()*+,;=:');
			$this->scheme_normalization();
		}

		return true;
	}

	/**
	 * Set the ihost. Returns true on success, false on failure (if there are
	 * any invalid characters).
	 *
	 * @param string $ihost
	 * @return bool
	 */
	protected function set_host($ihost) {
		if ($ihost === null) {
			$this->ihost = null;
			return true;
		}
		if (substr($ihost, 0, 1) === '[' && substr($ihost, -1) === ']') {
			if (Ipv6::check_ipv6(substr($ihost, 1, -1))) {
				$this->ihost = '[' . Ipv6::compress(substr($ihost, 1, -1)) . ']';
			}
			else {
				$this->ihost = null;
				return false;
			}
		}
		else {
			$ihost = $this->replace_invalid_with_pct_encoding($ihost, '!$&\'()*+,;=');

			// Lowercase, but ignore pct-encoded sections (as they should
			// remain uppercase). This must be done after the previous step
			// as that can add unescaped characters.
			$position = 0;
			$strlen = strlen($ihost);
			while (($position += strcspn($ihost, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ%', $position)) < $strlen) {
				if ($ihost[$position] === '%') {
					$position += 3;
				}
				else {
					$ihost[$position] = strtolower($ihost[$position]);
					$position++;
				}
			}

			$this->ihost = $ihost;
		}

		$this->scheme_normalization();

		return true;
	}

	/**
	 * Set the port. Returns true on success, false on failure (if there are
	 * any invalid characters).
	 *
	 * @param string $port
	 * @return bool
	 */
	protected function set_port($port) {
		if ($port === null) {
			$this->port = null;
			return true;
		}

		if (strspn($port, '0123456789') === strlen($port)) {
			$this->port = (int) $port;
			$this->scheme_normalization();
			return true;
		}

		$this->port = null;
		return false;
	}

	/**
	 * Set the ipath.
	 *
	 * @param string $ipath
	 * @return bool
	 */
	protected function set_path($ipath) {
		static $cache;
		if (!$cache) {
			$cache = array();
		}

		$ipath = (string) $ipath;

		if (isset($cache[$ipath])) {
			$this->ipath = $cache[$ipath][(int) ($this->scheme !== null)];
		}
		else {
			$valid = $this->replace_invalid_with_pct_encoding($ipath, '!$&\'()*+,;=@:/');
			$removed = $this->remove_dot_segments($valid);

			$cache[$ipath] = array($valid, $removed);
			$this->ipath = ($this->scheme !== null) ? $removed : $valid;
		}
		$this->scheme_normalization();
		return true;
	}

	/**
	 * Set the iquery.
	 *
	 * @param string $iquery
	 * @return bool
	 */
	protected function set_query($iquery) {
		if ($iquery === null) {
			$this->iquery = null;
		}
		else {
			$this->iquery = $this->replace_invalid_with_pct_encoding($iquery, '!$&\'()*+,;=:@/?', true);
			$this->scheme_normalization();
		}
		return true;
	}

	/**
	 * Set the ifragment.
	 *
	 * @param string $ifragment
	 * @return bool
	 */
	protected function set_fragment($ifragment) {
		if ($ifragment === null) {
			$this->ifragment = null;
		}
		else {
			$this->ifragment = $this->replace_invalid_with_pct_encoding($ifragment, '!$&\'()*+,;=:@/?');
			$this->scheme_normalization();
		}
		return true;
	}

	/**
	 * Convert an IRI to a URI (or parts thereof)
	 *
	 * @param string|bool $iri IRI to convert (or false from {@see \WpOrg\Requests\Iri::get_iri()})
	 * @return string|false URI if IRI is valid, false otherwise.
	 */
	protected function to_uri($iri) {
		if (!is_string($iri)) {
			return false;
		}

		static $non_ascii;
		if (!$non_ascii) {
			$non_ascii = implode('', range("\x80", "\xFF"));
		}

		$position = 0;
		$strlen = strlen($iri);
		while (($position += strcspn($iri, $non_ascii, $position)) < $strlen) {
			$iri = substr_replace($iri, sprintf('%%%02X', ord($iri[$position])), $position, 1);
			$position += 3;
			$strlen += 2;
		}

		return $iri;
	}

	/**
	 * Get the complete IRI
	 *
	 * @return string|false
	 */
	protected function get_iri() {
		if (!$this->is_valid()) {
			return false;
		}

		$iri = '';
		if ($this->scheme !== null) {
			$iri .= $this->scheme . ':';
		}
		if (($iauthority = $this->get_iauthority()) !== null) {
			$iri .= '//' . $iauthority;
		}
		$iri .= $this->ipath;
		if ($this->iquery !== null) {
			$iri .= '?' . $this->iquery;
		}
		if ($this->ifragment !== null) {
			$iri .= '#' . $this->ifragment;
		}

		return $iri;
	}

	/**
	 * Get the complete URI
	 *
	 * @return string
	 */
	protected function get_uri() {
		return $this->to_uri($this->get_iri());
	}

	/**
	 * Get the complete iauthority
	 *
	 * @return string|null
	 */
	protected function get_iauthority() {
		if ($this->iuserinfo === null && $this->ihost === null && $this->port === null) {
			return null;
		}

		$iauthority = '';
		if ($this->iuserinfo !== null) {
			$iauthority .= $this->iuserinfo . '@';
		}
		if ($this->ihost !== null) {
			$iauthority .= $this->ihost;
		}
		if ($this->port !== null) {
			$iauthority .= ':' . $this->port;
		}
		return $iauthority;
	}

	/**
	 * Get the complete authority
	 *
	 * @return string
	 */
	protected function get_authority() {
		$iauthority = $this->get_iauthority();
		if (is_string($iauthority)) {
			return $this->to_uri($iauthority);
		}
		else {
			return $iauthority;
		}
	}
}
PK��]��_ZZ
Exception.phpnu�[���<?php
/**
 * Exception for HTTP requests
 *
 * @package Requests\Exceptions
 */

namespace WpOrg\Requests;

use Exception as PHPException;

/**
 * Exception for HTTP requests
 *
 * @package Requests\Exceptions
 */
class Exception extends PHPException {
	/**
	 * Type of exception
	 *
	 * @var string
	 */
	protected $type;

	/**
	 * Data associated with the exception
	 *
	 * @var mixed
	 */
	protected $data;

	/**
	 * Create a new exception
	 *
	 * @param string $message Exception message
	 * @param string $type Exception type
	 * @param mixed $data Associated data
	 * @param integer $code Exception numerical code, if applicable
	 */
	public function __construct($message, $type, $data = null, $code = 0) {
		parent::__construct($message, $code);

		$this->type = $type;
		$this->data = $data;
	}

	/**
	 * Like {@see \Exception::getCode()}, but a string code.
	 *
	 * @codeCoverageIgnore
	 * @return string
	 */
	public function getType() {
		return $this->type;
	}

	/**
	 * Gives any relevant data
	 *
	 * @codeCoverageIgnore
	 * @return mixed
	 */
	public function getData() {
		return $this->data;
	}
}
PK��]��;�Ipv6.phpnu�[���<?php
/**
 * Class to validate and to work with IPv6 addresses
 *
 * @package Requests\Utilities
 */

namespace WpOrg\Requests;

use WpOrg\Requests\Exception\InvalidArgument;
use WpOrg\Requests\Utility\InputValidator;

/**
 * Class to validate and to work with IPv6 addresses
 *
 * This was originally based on the PEAR class of the same name, but has been
 * entirely rewritten.
 *
 * @package Requests\Utilities
 */
final class Ipv6 {
	/**
	 * Uncompresses an IPv6 address
	 *
	 * RFC 4291 allows you to compress consecutive zero pieces in an address to
	 * '::'. This method expects a valid IPv6 address and expands the '::' to
	 * the required number of zero pieces.
	 *
	 * Example:  FF01::101   ->  FF01:0:0:0:0:0:0:101
	 *           ::1         ->  0:0:0:0:0:0:0:1
	 *
	 * @author Alexander Merz <alexander.merz@web.de>
	 * @author elfrink at introweb dot nl
	 * @author Josh Peck <jmp at joshpeck dot org>
	 * @copyright 2003-2005 The PHP Group
	 * @license https://opensource.org/licenses/bsd-license.php
	 *
	 * @param string|Stringable $ip An IPv6 address
	 * @return string The uncompressed IPv6 address
	 *
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string or a stringable object.
	 */
	public static function uncompress($ip) {
		if (InputValidator::is_string_or_stringable($ip) === false) {
			throw InvalidArgument::create(1, '$ip', 'string|Stringable', gettype($ip));
		}

		$ip = (string) $ip;

		if (substr_count($ip, '::') !== 1) {
			return $ip;
		}

		list($ip1, $ip2) = explode('::', $ip);
		$c1              = ($ip1 === '') ? -1 : substr_count($ip1, ':');
		$c2              = ($ip2 === '') ? -1 : substr_count($ip2, ':');

		if (strpos($ip2, '.') !== false) {
			$c2++;
		}

		if ($c1 === -1 && $c2 === -1) {
			// ::
			$ip = '0:0:0:0:0:0:0:0';
		} elseif ($c1 === -1) {
			// ::xxx
			$fill = str_repeat('0:', 7 - $c2);
			$ip   = str_replace('::', $fill, $ip);
		} elseif ($c2 === -1) {
			// xxx::
			$fill = str_repeat(':0', 7 - $c1);
			$ip   = str_replace('::', $fill, $ip);
		} else {
			// xxx::xxx
			$fill = ':' . str_repeat('0:', 6 - $c2 - $c1);
			$ip   = str_replace('::', $fill, $ip);
		}

		return $ip;
	}

	/**
	 * Compresses an IPv6 address
	 *
	 * RFC 4291 allows you to compress consecutive zero pieces in an address to
	 * '::'. This method expects a valid IPv6 address and compresses consecutive
	 * zero pieces to '::'.
	 *
	 * Example:  FF01:0:0:0:0:0:0:101   ->  FF01::101
	 *           0:0:0:0:0:0:0:1        ->  ::1
	 *
	 * @see \WpOrg\Requests\Ipv6::uncompress()
	 *
	 * @param string $ip An IPv6 address
	 * @return string The compressed IPv6 address
	 */
	public static function compress($ip) {
		// Prepare the IP to be compressed.
		// Note: Input validation is handled in the `uncompress()` method, which is the first call made in this method.
		$ip       = self::uncompress($ip);
		$ip_parts = self::split_v6_v4($ip);

		// Replace all leading zeros
		$ip_parts[0] = preg_replace('/(^|:)0+([0-9])/', '\1\2', $ip_parts[0]);

		// Find bunches of zeros
		if (preg_match_all('/(?:^|:)(?:0(?::|$))+/', $ip_parts[0], $matches, PREG_OFFSET_CAPTURE)) {
			$max = 0;
			$pos = null;
			foreach ($matches[0] as $match) {
				if (strlen($match[0]) > $max) {
					$max = strlen($match[0]);
					$pos = $match[1];
				}
			}

			$ip_parts[0] = substr_replace($ip_parts[0], '::', $pos, $max);
		}

		if ($ip_parts[1] !== '') {
			return implode(':', $ip_parts);
		} else {
			return $ip_parts[0];
		}
	}

	/**
	 * Splits an IPv6 address into the IPv6 and IPv4 representation parts
	 *
	 * RFC 4291 allows you to represent the last two parts of an IPv6 address
	 * using the standard IPv4 representation
	 *
	 * Example:  0:0:0:0:0:0:13.1.68.3
	 *           0:0:0:0:0:FFFF:129.144.52.38
	 *
	 * @param string $ip An IPv6 address
	 * @return string[] [0] contains the IPv6 represented part, and [1] the IPv4 represented part
	 */
	private static function split_v6_v4($ip) {
		if (strpos($ip, '.') !== false) {
			$pos       = strrpos($ip, ':');
			$ipv6_part = substr($ip, 0, $pos);
			$ipv4_part = substr($ip, $pos + 1);
			return [$ipv6_part, $ipv4_part];
		} else {
			return [$ip, ''];
		}
	}

	/**
	 * Checks an IPv6 address
	 *
	 * Checks if the given IP is a valid IPv6 address
	 *
	 * @param string $ip An IPv6 address
	 * @return bool true if $ip is a valid IPv6 address
	 */
	public static function check_ipv6($ip) {
		// Note: Input validation is handled in the `uncompress()` method, which is the first call made in this method.
		$ip                = self::uncompress($ip);
		list($ipv6, $ipv4) = self::split_v6_v4($ip);
		$ipv6              = explode(':', $ipv6);
		$ipv4              = explode('.', $ipv4);
		if (count($ipv6) === 8 && count($ipv4) === 1 || count($ipv6) === 6 && count($ipv4) === 4) {
			foreach ($ipv6 as $ipv6_part) {
				// The section can't be empty
				if ($ipv6_part === '') {
					return false;
				}

				// Nor can it be over four characters
				if (strlen($ipv6_part) > 4) {
					return false;
				}

				// Remove leading zeros (this is safe because of the above)
				$ipv6_part = ltrim($ipv6_part, '0');
				if ($ipv6_part === '') {
					$ipv6_part = '0';
				}

				// Check the value is valid
				$value = hexdec($ipv6_part);
				if (dechex($value) !== strtolower($ipv6_part) || $value < 0 || $value > 0xFFFF) {
					return false;
				}
			}

			if (count($ipv4) === 4) {
				foreach ($ipv4 as $ipv4_part) {
					$value = (int) $ipv4_part;
					if ((string) $value !== $ipv4_part || $value < 0 || $value > 0xFF) {
						return false;
					}
				}
			}

			return true;
		} else {
			return false;
		}
	}
}
PK��]�����Port.phpnu�[���<?php
/**
 * Port utilities for Requests
 *
 * @package Requests\Utilities
 * @since   2.0.0
 */

namespace WpOrg\Requests;

use WpOrg\Requests\Exception;
use WpOrg\Requests\Exception\InvalidArgument;

/**
 * Find the correct port depending on the Request type.
 *
 * @package Requests\Utilities
 * @since   2.0.0
 */
final class Port {

	/**
	 * Port to use with Acap requests.
	 *
	 * @var int
	 */
	const ACAP = 674;

	/**
	 * Port to use with Dictionary requests.
	 *
	 * @var int
	 */
	const DICT = 2628;

	/**
	 * Port to use with HTTP requests.
	 *
	 * @var int
	 */
	const HTTP = 80;

	/**
	 * Port to use with HTTP over SSL requests.
	 *
	 * @var int
	 */
	const HTTPS = 443;

	/**
	 * Retrieve the port number to use.
	 *
	 * @param string $type Request type.
	 *                     The following requests types are supported:
	 *                     'acap', 'dict', 'http' and 'https'.
	 *
	 * @return int
	 *
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When a non-string input has been passed.
	 * @throws \WpOrg\Requests\Exception                 When a non-supported port is requested ('portnotsupported').
	 */
	public static function get($type) {
		if (!is_string($type)) {
			throw InvalidArgument::create(1, '$type', 'string', gettype($type));
		}

		$type = strtoupper($type);
		if (!defined("self::{$type}")) {
			$message = sprintf('Invalid port type (%s) passed', $type);
			throw new Exception($message, 'portnotsupported');
		}

		return constant("self::{$type}");
	}
}
PK��]���mmUtility/FilteredIterator.phpnu�[���<?php
/**
 * Iterator for arrays requiring filtered values
 *
 * @package Requests\Utilities
 */

namespace WpOrg\Requests\Utility;

use ArrayIterator;
use ReturnTypeWillChange;
use WpOrg\Requests\Exception\InvalidArgument;
use WpOrg\Requests\Utility\InputValidator;

/**
 * Iterator for arrays requiring filtered values
 *
 * @package Requests\Utilities
 */
final class FilteredIterator extends ArrayIterator {
	/**
	 * Callback to run as a filter
	 *
	 * @var callable
	 */
	private $callback;

	/**
	 * Create a new iterator
	 *
	 * @param array    $data     The array or object to be iterated on.
	 * @param callable $callback Callback to be called on each value
	 *
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $data argument is not iterable.
	 */
	public function __construct($data, $callback) {
		if (InputValidator::is_iterable($data) === false) {
			throw InvalidArgument::create(1, '$data', 'iterable', gettype($data));
		}

		parent::__construct($data);

		if (is_callable($callback)) {
			$this->callback = $callback;
		}
	}

	/**
	 * Prevent unserialization of the object for security reasons.
	 *
	 * @phpcs:disable PHPCompatibility.FunctionNameRestrictions.NewMagicMethods.__unserializeFound
	 *
	 * @param array $data Restored array of data originally serialized.
	 *
	 * @return void
	 */
	#[ReturnTypeWillChange]
	public function __unserialize($data) {}
	// phpcs:enable

	/**
	 * Perform reinitialization tasks.
	 *
	 * Prevents a callback from being injected during unserialization of an object.
	 *
	 * @return void
	 */
	public function __wakeup() {
		unset($this->callback);
	}

	/**
	 * Get the current item's value after filtering
	 *
	 * @return string
	 */
	#[ReturnTypeWillChange]
	public function current() {
		$value = parent::current();

		if (is_callable($this->callback)) {
			$value = call_user_func($this->callback, $value);
		}

		return $value;
	}

	/**
	 * Prevent creating a PHP value from a stored representation of the object for security reasons.
	 *
	 * @param string $data The serialized string.
	 *
	 * @return void
	 */
	#[ReturnTypeWillChange]
	public function unserialize($data) {}
}
PK��]�5L�	�	%Utility/CaseInsensitiveDictionary.phpnu�[���<?php
/**
 * Case-insensitive dictionary, suitable for HTTP headers
 *
 * @package Requests\Utilities
 */

namespace WpOrg\Requests\Utility;

use ArrayAccess;
use ArrayIterator;
use IteratorAggregate;
use ReturnTypeWillChange;
use WpOrg\Requests\Exception;

/**
 * Case-insensitive dictionary, suitable for HTTP headers
 *
 * @package Requests\Utilities
 */
class CaseInsensitiveDictionary implements ArrayAccess, IteratorAggregate {
	/**
	 * Actual item data
	 *
	 * @var array
	 */
	protected $data = [];

	/**
	 * Creates a case insensitive dictionary.
	 *
	 * @param array $data Dictionary/map to convert to case-insensitive
	 */
	public function __construct(array $data = []) {
		foreach ($data as $offset => $value) {
			$this->offsetSet($offset, $value);
		}
	}

	/**
	 * Check if the given item exists
	 *
	 * @param string $offset Item key
	 * @return boolean Does the item exist?
	 */
	#[ReturnTypeWillChange]
	public function offsetExists($offset) {
		if (is_string($offset)) {
			$offset = strtolower($offset);
		}

		return isset($this->data[$offset]);
	}

	/**
	 * Get the value for the item
	 *
	 * @param string $offset Item key
	 * @return string|null Item value (null if the item key doesn't exist)
	 */
	#[ReturnTypeWillChange]
	public function offsetGet($offset) {
		if (is_string($offset)) {
			$offset = strtolower($offset);
		}

		if (!isset($this->data[$offset])) {
			return null;
		}

		return $this->data[$offset];
	}

	/**
	 * Set the given item
	 *
	 * @param string $offset Item name
	 * @param string $value Item value
	 *
	 * @throws \WpOrg\Requests\Exception On attempting to use dictionary as list (`invalidset`)
	 */
	#[ReturnTypeWillChange]
	public function offsetSet($offset, $value) {
		if ($offset === null) {
			throw new Exception('Object is a dictionary, not a list', 'invalidset');
		}

		if (is_string($offset)) {
			$offset = strtolower($offset);
		}

		$this->data[$offset] = $value;
	}

	/**
	 * Unset the given header
	 *
	 * @param string $offset The key for the item to unset.
	 */
	#[ReturnTypeWillChange]
	public function offsetUnset($offset) {
		if (is_string($offset)) {
			$offset = strtolower($offset);
		}

		unset($this->data[$offset]);
	}

	/**
	 * Get an iterator for the data
	 *
	 * @return \ArrayIterator
	 */
	#[ReturnTypeWillChange]
	public function getIterator() {
		return new ArrayIterator($this->data);
	}

	/**
	 * Get the headers as an array
	 *
	 * @return array Header data
	 */
	public function getAll() {
		return $this->data;
	}
}
PK��]^�	��	�	Utility/InputValidator.phpnu�[���<?php
/**
 * Input validation utilities.
 *
 * @package Requests\Utilities
 */

namespace WpOrg\Requests\Utility;

use ArrayAccess;
use CurlHandle;
use Traversable;

/**
 * Input validation utilities.
 *
 * @package Requests\Utilities
 */
final class InputValidator {

	/**
	 * Verify that a received input parameter is of type string or is "stringable".
	 *
	 * @param mixed $input Input parameter to verify.
	 *
	 * @return bool
	 */
	public static function is_string_or_stringable($input) {
		return is_string($input) || self::is_stringable_object($input);
	}

	/**
	 * Verify whether a received input parameter is usable as an integer array key.
	 *
	 * @param mixed $input Input parameter to verify.
	 *
	 * @return bool
	 */
	public static function is_numeric_array_key($input) {
		if (is_int($input)) {
			return true;
		}

		if (!is_string($input)) {
			return false;
		}

		return (bool) preg_match('`^-?[0-9]+$`', $input);
	}

	/**
	 * Verify whether a received input parameter is "stringable".
	 *
	 * @param mixed $input Input parameter to verify.
	 *
	 * @return bool
	 */
	public static function is_stringable_object($input) {
		return is_object($input) && method_exists($input, '__toString');
	}

	/**
	 * Verify whether a received input parameter is _accessible as if it were an array_.
	 *
	 * @param mixed $input Input parameter to verify.
	 *
	 * @return bool
	 */
	public static function has_array_access($input) {
		return is_array($input) || $input instanceof ArrayAccess;
	}

	/**
	 * Verify whether a received input parameter is "iterable".
	 *
	 * @internal The PHP native `is_iterable()` function was only introduced in PHP 7.1
	 * and this library still supports PHP 5.6.
	 *
	 * @param mixed $input Input parameter to verify.
	 *
	 * @return bool
	 */
	public static function is_iterable($input) {
		return is_array($input) || $input instanceof Traversable;
	}

	/**
	 * Verify whether a received input parameter is a Curl handle.
	 *
	 * The PHP Curl extension worked with resources prior to PHP 8.0 and with
	 * an instance of the `CurlHandle` class since PHP 8.0.
	 * {@link https://www.php.net/manual/en/migration80.incompatible.php#migration80.incompatible.resource2object}
	 *
	 * @param mixed $input Input parameter to verify.
	 *
	 * @return bool
	 */
	public static function is_curl_handle($input) {
		if (is_resource($input)) {
			return get_resource_type($input) === 'curl';
		}

		if (is_object($input)) {
			return $input instanceof CurlHandle;
		}

		return false;
	}
}
PK��]g���ЄЄRequests.phpnu�[���<?php
/**
 * Requests for PHP
 *
 * Inspired by Requests for Python.
 *
 * Based on concepts from SimplePie_File, RequestCore and WP_Http.
 *
 * @package Requests
 */

namespace WpOrg\Requests;

use WpOrg\Requests\Auth\Basic;
use WpOrg\Requests\Capability;
use WpOrg\Requests\Cookie\Jar;
use WpOrg\Requests\Exception;
use WpOrg\Requests\Exception\InvalidArgument;
use WpOrg\Requests\Hooks;
use WpOrg\Requests\IdnaEncoder;
use WpOrg\Requests\Iri;
use WpOrg\Requests\Proxy\Http;
use WpOrg\Requests\Response;
use WpOrg\Requests\Transport\Curl;
use WpOrg\Requests\Transport\Fsockopen;
use WpOrg\Requests\Utility\InputValidator;

/**
 * Requests for PHP
 *
 * Inspired by Requests for Python.
 *
 * Based on concepts from SimplePie_File, RequestCore and WP_Http.
 *
 * @package Requests
 */
class Requests {
	/**
	 * POST method
	 *
	 * @var string
	 */
	const POST = 'POST';

	/**
	 * PUT method
	 *
	 * @var string
	 */
	const PUT = 'PUT';

	/**
	 * GET method
	 *
	 * @var string
	 */
	const GET = 'GET';

	/**
	 * HEAD method
	 *
	 * @var string
	 */
	const HEAD = 'HEAD';

	/**
	 * DELETE method
	 *
	 * @var string
	 */
	const DELETE = 'DELETE';

	/**
	 * OPTIONS method
	 *
	 * @var string
	 */
	const OPTIONS = 'OPTIONS';

	/**
	 * TRACE method
	 *
	 * @var string
	 */
	const TRACE = 'TRACE';

	/**
	 * PATCH method
	 *
	 * @link https://tools.ietf.org/html/rfc5789
	 * @var string
	 */
	const PATCH = 'PATCH';

	/**
	 * Default size of buffer size to read streams
	 *
	 * @var integer
	 */
	const BUFFER_SIZE = 1160;

	/**
	 * Option defaults.
	 *
	 * @see \WpOrg\Requests\Requests::get_default_options()
	 * @see \WpOrg\Requests\Requests::request() for values returned by this method
	 *
	 * @since 2.0.0
	 *
	 * @var array
	 */
	const OPTION_DEFAULTS = [
		'timeout'          => 10,
		'connect_timeout'  => 10,
		'useragent'        => 'php-requests/' . self::VERSION,
		'protocol_version' => 1.1,
		'redirected'       => 0,
		'redirects'        => 10,
		'follow_redirects' => true,
		'blocking'         => true,
		'type'             => self::GET,
		'filename'         => false,
		'auth'             => false,
		'proxy'            => false,
		'cookies'          => false,
		'max_bytes'        => false,
		'idn'              => true,
		'hooks'            => null,
		'transport'        => null,
		'verify'           => null,
		'verifyname'       => true,
	];

	/**
	 * Default supported Transport classes.
	 *
	 * @since 2.0.0
	 *
	 * @var array
	 */
	const DEFAULT_TRANSPORTS = [
		Curl::class      => Curl::class,
		Fsockopen::class => Fsockopen::class,
	];

	/**
	 * Current version of Requests
	 *
	 * @var string
	 */
	const VERSION = '2.0.9';

	/**
	 * Selected transport name
	 *
	 * Use {@see \WpOrg\Requests\Requests::get_transport()} instead
	 *
	 * @var array
	 */
	public static $transport = [];

	/**
	 * Registered transport classes
	 *
	 * @var array
	 */
	protected static $transports = [];

	/**
	 * Default certificate path.
	 *
	 * @see \WpOrg\Requests\Requests::get_certificate_path()
	 * @see \WpOrg\Requests\Requests::set_certificate_path()
	 *
	 * @var string
	 */
	protected static $certificate_path = __DIR__ . '/../certificates/cacert.pem';

	/**
	 * All (known) valid deflate, gzip header magic markers.
	 *
	 * These markers relate to different compression levels.
	 *
	 * @link https://stackoverflow.com/a/43170354/482864 Marker source.
	 *
	 * @since 2.0.0
	 *
	 * @var array
	 */
	private static $magic_compression_headers = [
		"\x1f\x8b" => true, // Gzip marker.
		"\x78\x01" => true, // Zlib marker - level 1.
		"\x78\x5e" => true, // Zlib marker - level 2 to 5.
		"\x78\x9c" => true, // Zlib marker - level 6.
		"\x78\xda" => true, // Zlib marker - level 7 to 9.
	];

	/**
	 * This is a static class, do not instantiate it
	 *
	 * @codeCoverageIgnore
	 */
	private function __construct() {}

	/**
	 * Register a transport
	 *
	 * @param string $transport Transport class to add, must support the \WpOrg\Requests\Transport interface
	 */
	public static function add_transport($transport) {
		if (empty(self::$transports)) {
			self::$transports = self::DEFAULT_TRANSPORTS;
		}

		self::$transports[$transport] = $transport;
	}

	/**
	 * Get the fully qualified class name (FQCN) for a working transport.
	 *
	 * @param array<string, bool> $capabilities Optional. Associative array of capabilities to test against, i.e. `['<capability>' => true]`.
	 * @return string FQCN of the transport to use, or an empty string if no transport was
	 *                found which provided the requested capabilities.
	 */
	protected static function get_transport_class(array $capabilities = []) {
		// Caching code, don't bother testing coverage.
		// @codeCoverageIgnoreStart
		// Array of capabilities as a string to be used as an array key.
		ksort($capabilities);
		$cap_string = serialize($capabilities);

		// Don't search for a transport if it's already been done for these $capabilities.
		if (isset(self::$transport[$cap_string])) {
			return self::$transport[$cap_string];
		}

		// Ensure we will not run this same check again later on.
		self::$transport[$cap_string] = '';
		// @codeCoverageIgnoreEnd

		if (empty(self::$transports)) {
			self::$transports = self::DEFAULT_TRANSPORTS;
		}

		// Find us a working transport.
		foreach (self::$transports as $class) {
			if (!class_exists($class)) {
				continue;
			}

			$result = $class::test($capabilities);
			if ($result === true) {
				self::$transport[$cap_string] = $class;
				break;
			}
		}

		return self::$transport[$cap_string];
	}

	/**
	 * Get a working transport.
	 *
	 * @param array<string, bool> $capabilities Optional. Associative array of capabilities to test against, i.e. `['<capability>' => true]`.
	 * @return \WpOrg\Requests\Transport
	 * @throws \WpOrg\Requests\Exception If no valid transport is found (`notransport`).
	 */
	protected static function get_transport(array $capabilities = []) {
		$class = self::get_transport_class($capabilities);

		if ($class === '') {
			throw new Exception('No working transports found', 'notransport', self::$transports);
		}

		return new $class();
	}

	/**
	 * Checks to see if we have a transport for the capabilities requested.
	 *
	 * Supported capabilities can be found in the {@see \WpOrg\Requests\Capability}
	 * interface as constants.
	 *
	 * Example usage:
	 * `Requests::has_capabilities([Capability::SSL => true])`.
	 *
	 * @param array<string, bool> $capabilities Optional. Associative array of capabilities to test against, i.e. `['<capability>' => true]`.
	 * @return bool Whether the transport has the requested capabilities.
	 */
	public static function has_capabilities(array $capabilities = []) {
		return self::get_transport_class($capabilities) !== '';
	}

	/**#@+
	 * @see \WpOrg\Requests\Requests::request()
	 * @param string $url
	 * @param array $headers
	 * @param array $options
	 * @return \WpOrg\Requests\Response
	 */
	/**
	 * Send a GET request
	 */
	public static function get($url, $headers = [], $options = []) {
		return self::request($url, $headers, null, self::GET, $options);
	}

	/**
	 * Send a HEAD request
	 */
	public static function head($url, $headers = [], $options = []) {
		return self::request($url, $headers, null, self::HEAD, $options);
	}

	/**
	 * Send a DELETE request
	 */
	public static function delete($url, $headers = [], $options = []) {
		return self::request($url, $headers, null, self::DELETE, $options);
	}

	/**
	 * Send a TRACE request
	 */
	public static function trace($url, $headers = [], $options = []) {
		return self::request($url, $headers, null, self::TRACE, $options);
	}
	/**#@-*/

	/**#@+
	 * @see \WpOrg\Requests\Requests::request()
	 * @param string $url
	 * @param array $headers
	 * @param array $data
	 * @param array $options
	 * @return \WpOrg\Requests\Response
	 */
	/**
	 * Send a POST request
	 */
	public static function post($url, $headers = [], $data = [], $options = []) {
		return self::request($url, $headers, $data, self::POST, $options);
	}
	/**
	 * Send a PUT request
	 */
	public static function put($url, $headers = [], $data = [], $options = []) {
		return self::request($url, $headers, $data, self::PUT, $options);
	}

	/**
	 * Send an OPTIONS request
	 */
	public static function options($url, $headers = [], $data = [], $options = []) {
		return self::request($url, $headers, $data, self::OPTIONS, $options);
	}

	/**
	 * Send a PATCH request
	 *
	 * Note: Unlike {@see \WpOrg\Requests\Requests::post()} and {@see \WpOrg\Requests\Requests::put()},
	 * `$headers` is required, as the specification recommends that should send an ETag
	 *
	 * @link https://tools.ietf.org/html/rfc5789
	 */
	public static function patch($url, $headers, $data = [], $options = []) {
		return self::request($url, $headers, $data, self::PATCH, $options);
	}
	/**#@-*/

	/**
	 * Main interface for HTTP requests
	 *
	 * This method initiates a request and sends it via a transport before
	 * parsing.
	 *
	 * The `$options` parameter takes an associative array with the following
	 * options:
	 *
	 * - `timeout`: How long should we wait for a response?
	 *    Note: for cURL, a minimum of 1 second applies, as DNS resolution
	 *    operates at second-resolution only.
	 *    (float, seconds with a millisecond precision, default: 10, example: 0.01)
	 * - `connect_timeout`: How long should we wait while trying to connect?
	 *    (float, seconds with a millisecond precision, default: 10, example: 0.01)
	 * - `useragent`: Useragent to send to the server
	 *    (string, default: php-requests/$version)
	 * - `follow_redirects`: Should we follow 3xx redirects?
	 *    (boolean, default: true)
	 * - `redirects`: How many times should we redirect before erroring?
	 *    (integer, default: 10)
	 * - `blocking`: Should we block processing on this request?
	 *    (boolean, default: true)
	 * - `filename`: File to stream the body to instead.
	 *    (string|boolean, default: false)
	 * - `auth`: Authentication handler or array of user/password details to use
	 *    for Basic authentication
	 *    (\WpOrg\Requests\Auth|array|boolean, default: false)
	 * - `proxy`: Proxy details to use for proxy by-passing and authentication
	 *    (\WpOrg\Requests\Proxy|array|string|boolean, default: false)
	 * - `max_bytes`: Limit for the response body size.
	 *    (integer|boolean, default: false)
	 * - `idn`: Enable IDN parsing
	 *    (boolean, default: true)
	 * - `transport`: Custom transport. Either a class name, or a
	 *    transport object. Defaults to the first working transport from
	 *    {@see \WpOrg\Requests\Requests::getTransport()}
	 *    (string|\WpOrg\Requests\Transport, default: {@see \WpOrg\Requests\Requests::getTransport()})
	 * - `hooks`: Hooks handler.
	 *    (\WpOrg\Requests\HookManager, default: new WpOrg\Requests\Hooks())
	 * - `verify`: Should we verify SSL certificates? Allows passing in a custom
	 *    certificate file as a string. (Using true uses the system-wide root
	 *    certificate store instead, but this may have different behaviour
	 *    across transports.)
	 *    (string|boolean, default: certificates/cacert.pem)
	 * - `verifyname`: Should we verify the common name in the SSL certificate?
	 *    (boolean, default: true)
	 * - `data_format`: How should we send the `$data` parameter?
	 *    (string, one of 'query' or 'body', default: 'query' for
	 *    HEAD/GET/DELETE, 'body' for POST/PUT/OPTIONS/PATCH)
	 *
	 * @param string|Stringable $url URL to request
	 * @param array $headers Extra headers to send with the request
	 * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests
	 * @param string $type HTTP request type (use Requests constants)
	 * @param array $options Options for the request (see description for more information)
	 * @return \WpOrg\Requests\Response
	 *
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $url argument is not a string or Stringable.
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $type argument is not a string.
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array.
	 * @throws \WpOrg\Requests\Exception On invalid URLs (`nonhttp`)
	 */
	public static function request($url, $headers = [], $data = [], $type = self::GET, $options = []) {
		if (InputValidator::is_string_or_stringable($url) === false) {
			throw InvalidArgument::create(1, '$url', 'string|Stringable', gettype($url));
		}

		if (is_string($type) === false) {
			throw InvalidArgument::create(4, '$type', 'string', gettype($type));
		}

		if (is_array($options) === false) {
			throw InvalidArgument::create(5, '$options', 'array', gettype($options));
		}

		if (empty($options['type'])) {
			$options['type'] = $type;
		}

		$options = array_merge(self::get_default_options(), $options);

		self::set_defaults($url, $headers, $data, $type, $options);

		$options['hooks']->dispatch('requests.before_request', [&$url, &$headers, &$data, &$type, &$options]);

		if (!empty($options['transport'])) {
			$transport = $options['transport'];

			if (is_string($options['transport'])) {
				$transport = new $transport();
			}
		} else {
			$need_ssl     = (stripos($url, 'https://') === 0);
			$capabilities = [Capability::SSL => $need_ssl];
			$transport    = self::get_transport($capabilities);
		}

		$response = $transport->request($url, $headers, $data, $options);

		$options['hooks']->dispatch('requests.before_parse', [&$response, $url, $headers, $data, $type, $options]);

		return self::parse_response($response, $url, $headers, $data, $options);
	}

	/**
	 * Send multiple HTTP requests simultaneously
	 *
	 * The `$requests` parameter takes an associative or indexed array of
	 * request fields. The key of each request can be used to match up the
	 * request with the returned data, or with the request passed into your
	 * `multiple.request.complete` callback.
	 *
	 * The request fields value is an associative array with the following keys:
	 *
	 * - `url`: Request URL Same as the `$url` parameter to
	 *    {@see \WpOrg\Requests\Requests::request()}
	 *    (string, required)
	 * - `headers`: Associative array of header fields. Same as the `$headers`
	 *    parameter to {@see \WpOrg\Requests\Requests::request()}
	 *    (array, default: `array()`)
	 * - `data`: Associative array of data fields or a string. Same as the
	 *    `$data` parameter to {@see \WpOrg\Requests\Requests::request()}
	 *    (array|string, default: `array()`)
	 * - `type`: HTTP request type (use \WpOrg\Requests\Requests constants). Same as the `$type`
	 *    parameter to {@see \WpOrg\Requests\Requests::request()}
	 *    (string, default: `\WpOrg\Requests\Requests::GET`)
	 * - `cookies`: Associative array of cookie name to value, or cookie jar.
	 *    (array|\WpOrg\Requests\Cookie\Jar)
	 *
	 * If the `$options` parameter is specified, individual requests will
	 * inherit options from it. This can be used to use a single hooking system,
	 * or set all the types to `\WpOrg\Requests\Requests::POST`, for example.
	 *
	 * In addition, the `$options` parameter takes the following global options:
	 *
	 * - `complete`: A callback for when a request is complete. Takes two
	 *    parameters, a \WpOrg\Requests\Response/\WpOrg\Requests\Exception reference, and the
	 *    ID from the request array (Note: this can also be overridden on a
	 *    per-request basis, although that's a little silly)
	 *    (callback)
	 *
	 * @param array $requests Requests data (see description for more information)
	 * @param array $options Global and default options (see {@see \WpOrg\Requests\Requests::request()})
	 * @return array Responses (either \WpOrg\Requests\Response or a \WpOrg\Requests\Exception object)
	 *
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $requests argument is not an array or iterable object with array access.
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array.
	 */
	public static function request_multiple($requests, $options = []) {
		if (InputValidator::has_array_access($requests) === false || InputValidator::is_iterable($requests) === false) {
			throw InvalidArgument::create(1, '$requests', 'array|ArrayAccess&Traversable', gettype($requests));
		}

		if (is_array($options) === false) {
			throw InvalidArgument::create(2, '$options', 'array', gettype($options));
		}

		$options = array_merge(self::get_default_options(true), $options);

		if (!empty($options['hooks'])) {
			$options['hooks']->register('transport.internal.parse_response', [static::class, 'parse_multiple']);
			if (!empty($options['complete'])) {
				$options['hooks']->register('multiple.request.complete', $options['complete']);
			}
		}

		foreach ($requests as $id => &$request) {
			if (!isset($request['headers'])) {
				$request['headers'] = [];
			}

			if (!isset($request['data'])) {
				$request['data'] = [];
			}

			if (!isset($request['type'])) {
				$request['type'] = self::GET;
			}

			if (!isset($request['options'])) {
				$request['options']         = $options;
				$request['options']['type'] = $request['type'];
			} else {
				if (empty($request['options']['type'])) {
					$request['options']['type'] = $request['type'];
				}

				$request['options'] = array_merge($options, $request['options']);
			}

			self::set_defaults($request['url'], $request['headers'], $request['data'], $request['type'], $request['options']);

			// Ensure we only hook in once
			if ($request['options']['hooks'] !== $options['hooks']) {
				$request['options']['hooks']->register('transport.internal.parse_response', [static::class, 'parse_multiple']);
				if (!empty($request['options']['complete'])) {
					$request['options']['hooks']->register('multiple.request.complete', $request['options']['complete']);
				}
			}
		}

		unset($request);

		if (!empty($options['transport'])) {
			$transport = $options['transport'];

			if (is_string($options['transport'])) {
				$transport = new $transport();
			}
		} else {
			$transport = self::get_transport();
		}

		$responses = $transport->request_multiple($requests, $options);

		foreach ($responses as $id => &$response) {
			// If our hook got messed with somehow, ensure we end up with the
			// correct response
			if (is_string($response)) {
				$request = $requests[$id];
				self::parse_multiple($response, $request);
				$request['options']['hooks']->dispatch('multiple.request.complete', [&$response, $id]);
			}
		}

		return $responses;
	}

	/**
	 * Get the default options
	 *
	 * @see \WpOrg\Requests\Requests::request() for values returned by this method
	 * @param boolean $multirequest Is this a multirequest?
	 * @return array Default option values
	 */
	protected static function get_default_options($multirequest = false) {
		$defaults           = static::OPTION_DEFAULTS;
		$defaults['verify'] = self::$certificate_path;

		if ($multirequest !== false) {
			$defaults['complete'] = null;
		}

		return $defaults;
	}

	/**
	 * Get default certificate path.
	 *
	 * @return string Default certificate path.
	 */
	public static function get_certificate_path() {
		return self::$certificate_path;
	}

	/**
	 * Set default certificate path.
	 *
	 * @param string|Stringable|bool $path Certificate path, pointing to a PEM file.
	 *
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $url argument is not a string, Stringable or boolean.
	 */
	public static function set_certificate_path($path) {
		if (InputValidator::is_string_or_stringable($path) === false && is_bool($path) === false) {
			throw InvalidArgument::create(1, '$path', 'string|Stringable|bool', gettype($path));
		}

		self::$certificate_path = $path;
	}

	/**
	 * Set the default values
	 *
	 * The $options parameter is updated with the results.
	 *
	 * @param string $url URL to request
	 * @param array $headers Extra headers to send with the request
	 * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests
	 * @param string $type HTTP request type
	 * @param array $options Options for the request
	 * @return void
	 *
	 * @throws \WpOrg\Requests\Exception When the $url is not an http(s) URL.
	 */
	protected static function set_defaults(&$url, &$headers, &$data, &$type, &$options) {
		if (!preg_match('/^http(s)?:\/\//i', $url, $matches)) {
			throw new Exception('Only HTTP(S) requests are handled.', 'nonhttp', $url);
		}

		if (empty($options['hooks'])) {
			$options['hooks'] = new Hooks();
		}

		if (is_array($options['auth'])) {
			$options['auth'] = new Basic($options['auth']);
		}

		if ($options['auth'] !== false) {
			$options['auth']->register($options['hooks']);
		}

		if (is_string($options['proxy']) || is_array($options['proxy'])) {
			$options['proxy'] = new Http($options['proxy']);
		}

		if ($options['proxy'] !== false) {
			$options['proxy']->register($options['hooks']);
		}

		if (is_array($options['cookies'])) {
			$options['cookies'] = new Jar($options['cookies']);
		} elseif (empty($options['cookies'])) {
			$options['cookies'] = new Jar();
		}

		if ($options['cookies'] !== false) {
			$options['cookies']->register($options['hooks']);
		}

		if ($options['idn'] !== false) {
			$iri       = new Iri($url);
			$iri->host = IdnaEncoder::encode($iri->ihost);
			$url       = $iri->uri;
		}

		// Massage the type to ensure we support it.
		$type = strtoupper($type);

		if (!isset($options['data_format'])) {
			if (in_array($type, [self::HEAD, self::GET, self::DELETE], true)) {
				$options['data_format'] = 'query';
			} else {
				$options['data_format'] = 'body';
			}
		}
	}

	/**
	 * HTTP response parser
	 *
	 * @param string $headers Full response text including headers and body
	 * @param string $url Original request URL
	 * @param array $req_headers Original $headers array passed to {@link request()}, in case we need to follow redirects
	 * @param array $req_data Original $data array passed to {@link request()}, in case we need to follow redirects
	 * @param array $options Original $options array passed to {@link request()}, in case we need to follow redirects
	 * @return \WpOrg\Requests\Response
	 *
	 * @throws \WpOrg\Requests\Exception On missing head/body separator (`requests.no_crlf_separator`)
	 * @throws \WpOrg\Requests\Exception On missing head/body separator (`noversion`)
	 * @throws \WpOrg\Requests\Exception On missing head/body separator (`toomanyredirects`)
	 */
	protected static function parse_response($headers, $url, $req_headers, $req_data, $options) {
		$return = new Response();
		if (!$options['blocking']) {
			return $return;
		}

		$return->raw  = $headers;
		$return->url  = (string) $url;
		$return->body = '';

		if (!$options['filename']) {
			$pos = strpos($headers, "\r\n\r\n");
			if ($pos === false) {
				// Crap!
				throw new Exception('Missing header/body separator', 'requests.no_crlf_separator');
			}

			$headers = substr($return->raw, 0, $pos);
			// Headers will always be separated from the body by two new lines - `\n\r\n\r`.
			$body = substr($return->raw, $pos + 4);
			if (!empty($body)) {
				$return->body = $body;
			}
		}

		// Pretend CRLF = LF for compatibility (RFC 2616, section 19.3)
		$headers = str_replace("\r\n", "\n", $headers);
		// Unfold headers (replace [CRLF] 1*( SP | HT ) with SP) as per RFC 2616 (section 2.2)
		$headers = preg_replace('/\n[ \t]/', ' ', $headers);
		$headers = explode("\n", $headers);
		preg_match('#^HTTP/(1\.\d)[ \t]+(\d+)#i', array_shift($headers), $matches);
		if (empty($matches)) {
			throw new Exception('Response could not be parsed', 'noversion', $headers);
		}

		$return->protocol_version = (float) $matches[1];
		$return->status_code      = (int) $matches[2];
		if ($return->status_code >= 200 && $return->status_code < 300) {
			$return->success = true;
		}

		foreach ($headers as $header) {
			list($key, $value) = explode(':', $header, 2);
			$value             = trim($value);
			preg_replace('#(\s+)#i', ' ', $value);
			$return->headers[$key] = $value;
		}

		if (isset($return->headers['transfer-encoding'])) {
			$return->body = self::decode_chunked($return->body);
			unset($return->headers['transfer-encoding']);
		}

		if (isset($return->headers['content-encoding'])) {
			$return->body = self::decompress($return->body);
		}

		//fsockopen and cURL compatibility
		if (isset($return->headers['connection'])) {
			unset($return->headers['connection']);
		}

		$options['hooks']->dispatch('requests.before_redirect_check', [&$return, $req_headers, $req_data, $options]);

		if ($return->is_redirect() && $options['follow_redirects'] === true) {
			if (isset($return->headers['location']) && $options['redirected'] < $options['redirects']) {
				if ($return->status_code === 303) {
					$options['type'] = self::GET;
				}

				$options['redirected']++;
				$location = $return->headers['location'];
				if (strpos($location, 'http://') !== 0 && strpos($location, 'https://') !== 0) {
					// relative redirect, for compatibility make it absolute
					$location = Iri::absolutize($url, $location);
					$location = $location->uri;
				}

				$hook_args = [
					&$location,
					&$req_headers,
					&$req_data,
					&$options,
					$return,
				];
				$options['hooks']->dispatch('requests.before_redirect', $hook_args);
				$redirected            = self::request($location, $req_headers, $req_data, $options['type'], $options);
				$redirected->history[] = $return;
				return $redirected;
			} elseif ($options['redirected'] >= $options['redirects']) {
				throw new Exception('Too many redirects', 'toomanyredirects', $return);
			}
		}

		$return->redirects = $options['redirected'];

		$options['hooks']->dispatch('requests.after_request', [&$return, $req_headers, $req_data, $options]);
		return $return;
	}

	/**
	 * Callback for `transport.internal.parse_response`
	 *
	 * Internal use only. Converts a raw HTTP response to a \WpOrg\Requests\Response
	 * while still executing a multiple request.
	 *
	 * `$response` is either set to a \WpOrg\Requests\Response instance, or a \WpOrg\Requests\Exception object
	 *
	 * @param string $response Full response text including headers and body (will be overwritten with Response instance)
	 * @param array $request Request data as passed into {@see \WpOrg\Requests\Requests::request_multiple()}
	 * @return void
	 */
	public static function parse_multiple(&$response, $request) {
		try {
			$url      = $request['url'];
			$headers  = $request['headers'];
			$data     = $request['data'];
			$options  = $request['options'];
			$response = self::parse_response($response, $url, $headers, $data, $options);
		} catch (Exception $e) {
			$response = $e;
		}
	}

	/**
	 * Decoded a chunked body as per RFC 2616
	 *
	 * @link https://tools.ietf.org/html/rfc2616#section-3.6.1
	 * @param string $data Chunked body
	 * @return string Decoded body
	 */
	protected static function decode_chunked($data) {
		if (!preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i', trim($data))) {
			return $data;
		}

		$decoded = '';
		$encoded = $data;

		while (true) {
			$is_chunked = (bool) preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i', $encoded, $matches);
			if (!$is_chunked) {
				// Looks like it's not chunked after all
				return $data;
			}

			$length = hexdec(trim($matches[1]));
			if ($length === 0) {
				// Ignore trailer headers
				return $decoded;
			}

			$chunk_length = strlen($matches[0]);
			$decoded     .= substr($encoded, $chunk_length, $length);
			$encoded      = substr($encoded, $chunk_length + $length + 2);

			if (trim($encoded) === '0' || empty($encoded)) {
				return $decoded;
			}
		}

		// We'll never actually get down here
		// @codeCoverageIgnoreStart
	}
	// @codeCoverageIgnoreEnd

	/**
	 * Convert a key => value array to a 'key: value' array for headers
	 *
	 * @param iterable $dictionary Dictionary of header values
	 * @return array List of headers
	 *
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not iterable.
	 */
	public static function flatten($dictionary) {
		if (InputValidator::is_iterable($dictionary) === false) {
			throw InvalidArgument::create(1, '$dictionary', 'iterable', gettype($dictionary));
		}

		$return = [];
		foreach ($dictionary as $key => $value) {
			$return[] = sprintf('%s: %s', $key, $value);
		}

		return $return;
	}

	/**
	 * Decompress an encoded body
	 *
	 * Implements gzip, compress and deflate. Guesses which it is by attempting
	 * to decode.
	 *
	 * @param string $data Compressed data in one of the above formats
	 * @return string Decompressed string
	 *
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string.
	 */
	public static function decompress($data) {
		if (is_string($data) === false) {
			throw InvalidArgument::create(1, '$data', 'string', gettype($data));
		}

		if (trim($data) === '') {
			// Empty body does not need further processing.
			return $data;
		}

		$marker = substr($data, 0, 2);
		if (!isset(self::$magic_compression_headers[$marker])) {
			// Not actually compressed. Probably cURL ruining this for us.
			return $data;
		}

		if (function_exists('gzdecode')) {
			$decoded = @gzdecode($data);
			if ($decoded !== false) {
				return $decoded;
			}
		}

		if (function_exists('gzinflate')) {
			$decoded = @gzinflate($data);
			if ($decoded !== false) {
				return $decoded;
			}
		}

		$decoded = self::compatible_gzinflate($data);
		if ($decoded !== false) {
			return $decoded;
		}

		if (function_exists('gzuncompress')) {
			$decoded = @gzuncompress($data);
			if ($decoded !== false) {
				return $decoded;
			}
		}

		return $data;
	}

	/**
	 * Decompression of deflated string while staying compatible with the majority of servers.
	 *
	 * Certain Servers will return deflated data with headers which PHP's gzinflate()
	 * function cannot handle out of the box. The following function has been created from
	 * various snippets on the gzinflate() PHP documentation.
	 *
	 * Warning: Magic numbers within. Due to the potential different formats that the compressed
	 * data may be returned in, some "magic offsets" are needed to ensure proper decompression
	 * takes place. For a simple progmatic way to determine the magic offset in use, see:
	 * https://core.trac.wordpress.org/ticket/18273
	 *
	 * @since 1.6.0
	 * @link https://core.trac.wordpress.org/ticket/18273
	 * @link https://www.php.net/gzinflate#70875
	 * @link https://www.php.net/gzinflate#77336
	 *
	 * @param string $gz_data String to decompress.
	 * @return string|bool False on failure.
	 *
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string.
	 */
	public static function compatible_gzinflate($gz_data) {
		if (is_string($gz_data) === false) {
			throw InvalidArgument::create(1, '$gz_data', 'string', gettype($gz_data));
		}

		if (trim($gz_data) === '') {
			return false;
		}

		// Compressed data might contain a full zlib header, if so strip it for
		// gzinflate()
		if (substr($gz_data, 0, 3) === "\x1f\x8b\x08") {
			$i   = 10;
			$flg = ord(substr($gz_data, 3, 1));
			if ($flg > 0) {
				if ($flg & 4) {
					list($xlen) = unpack('v', substr($gz_data, $i, 2));
					$i         += 2 + $xlen;
				}

				if ($flg & 8) {
					$i = strpos($gz_data, "\0", $i) + 1;
				}

				if ($flg & 16) {
					$i = strpos($gz_data, "\0", $i) + 1;
				}

				if ($flg & 2) {
					$i += 2;
				}
			}

			$decompressed = self::compatible_gzinflate(substr($gz_data, $i));
			if ($decompressed !== false) {
				return $decompressed;
			}
		}

		// If the data is Huffman Encoded, we must first strip the leading 2
		// byte Huffman marker for gzinflate()
		// The response is Huffman coded by many compressors such as
		// java.util.zip.Deflater, Ruby's Zlib::Deflate, and .NET's
		// System.IO.Compression.DeflateStream.
		//
		// See https://decompres.blogspot.com/ for a quick explanation of this
		// data type
		$huffman_encoded = false;

		// low nibble of first byte should be 0x08
		list(, $first_nibble) = unpack('h', $gz_data);

		// First 2 bytes should be divisible by 0x1F
		list(, $first_two_bytes) = unpack('n', $gz_data);

		if ($first_nibble === 0x08 && ($first_two_bytes % 0x1F) === 0) {
			$huffman_encoded = true;
		}

		if ($huffman_encoded) {
			$decompressed = @gzinflate(substr($gz_data, 2));
			if ($decompressed !== false) {
				return $decompressed;
			}
		}

		if (substr($gz_data, 0, 4) === "\x50\x4b\x03\x04") {
			// ZIP file format header
			// Offset 6: 2 bytes, General-purpose field
			// Offset 26: 2 bytes, filename length
			// Offset 28: 2 bytes, optional field length
			// Offset 30: Filename field, followed by optional field, followed
			// immediately by data
			list(, $general_purpose_flag) = unpack('v', substr($gz_data, 6, 2));

			// If the file has been compressed on the fly, 0x08 bit is set of
			// the general purpose field. We can use this to differentiate
			// between a compressed document, and a ZIP file
			$zip_compressed_on_the_fly = ((0x08 & $general_purpose_flag) === 0x08);

			if (!$zip_compressed_on_the_fly) {
				// Don't attempt to decode a compressed zip file
				return $gz_data;
			}

			// Determine the first byte of data, based on the above ZIP header
			// offsets:
			$first_file_start = array_sum(unpack('v2', substr($gz_data, 26, 4)));
			$decompressed     = @gzinflate(substr($gz_data, 30 + $first_file_start));
			if ($decompressed !== false) {
				return $decompressed;
			}

			return false;
		}

		// Finally fall back to straight gzinflate
		$decompressed = @gzinflate($gz_data);
		if ($decompressed !== false) {
			return $decompressed;
		}

		// Fallback for all above failing, not expected, but included for
		// debugging and preventing regressions and to track stats
		$decompressed = @gzinflate(substr($gz_data, 2));
		if ($decompressed !== false) {
			return $decompressed;
		}

		return false;
	}
}
PK��]�Մecc	Proxy.phpnu�[���<?php
/**
 * Proxy connection interface
 *
 * @package Requests\Proxy
 * @since   1.6
 */

namespace WpOrg\Requests;

use WpOrg\Requests\Hooks;

/**
 * Proxy connection interface
 *
 * Implement this interface to handle proxy settings and authentication
 *
 * Parameters should be passed via the constructor where possible, as this
 * makes it much easier for users to use your provider.
 *
 * @see \WpOrg\Requests\Hooks
 *
 * @package Requests\Proxy
 * @since   1.6
 */
interface Proxy {
	/**
	 * Register hooks as needed
	 *
	 * This method is called in {@see \WpOrg\Requests\Requests::request()} when the user
	 * has set an instance as the 'auth' option. Use this callback to register all the
	 * hooks you'll need.
	 *
	 * @see \WpOrg\Requests\Hooks::register()
	 * @param \WpOrg\Requests\Hooks $hooks Hook system
	 */
	public function register(Hooks $hooks);
}
PK��]�M���0�0IdnaEncoder.phpnu�[���<?php

namespace WpOrg\Requests;

use WpOrg\Requests\Exception;
use WpOrg\Requests\Exception\InvalidArgument;
use WpOrg\Requests\Utility\InputValidator;

/**
 * IDNA URL encoder
 *
 * Note: Not fully compliant, as nameprep does nothing yet.
 *
 * @package Requests\Utilities
 *
 * @link https://tools.ietf.org/html/rfc3490 IDNA specification
 * @link https://tools.ietf.org/html/rfc3492 Punycode/Bootstrap specification
 */
class IdnaEncoder {
	/**
	 * ACE prefix used for IDNA
	 *
	 * @link https://tools.ietf.org/html/rfc3490#section-5
	 * @var string
	 */
	const ACE_PREFIX = 'xn--';

	/**
	 * Maximum length of a IDNA URL in ASCII.
	 *
	 * @see \WpOrg\Requests\IdnaEncoder::to_ascii()
	 *
	 * @since 2.0.0
	 *
	 * @var int
	 */
	const MAX_LENGTH = 64;

	/**#@+
	 * Bootstrap constant for Punycode
	 *
	 * @link https://tools.ietf.org/html/rfc3492#section-5
	 * @var int
	 */
	const BOOTSTRAP_BASE         = 36;
	const BOOTSTRAP_TMIN         = 1;
	const BOOTSTRAP_TMAX         = 26;
	const BOOTSTRAP_SKEW         = 38;
	const BOOTSTRAP_DAMP         = 700;
	const BOOTSTRAP_INITIAL_BIAS = 72;
	const BOOTSTRAP_INITIAL_N    = 128;
	/**#@-*/

	/**
	 * Encode a hostname using Punycode
	 *
	 * @param string|Stringable $hostname Hostname
	 * @return string Punycode-encoded hostname
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string or a stringable object.
	 */
	public static function encode($hostname) {
		if (InputValidator::is_string_or_stringable($hostname) === false) {
			throw InvalidArgument::create(1, '$hostname', 'string|Stringable', gettype($hostname));
		}

		$parts = explode('.', $hostname);
		foreach ($parts as &$part) {
			$part = self::to_ascii($part);
		}

		return implode('.', $parts);
	}

	/**
	 * Convert a UTF-8 text string to an ASCII string using Punycode
	 *
	 * @param string $text ASCII or UTF-8 string (max length 64 characters)
	 * @return string ASCII string
	 *
	 * @throws \WpOrg\Requests\Exception Provided string longer than 64 ASCII characters (`idna.provided_too_long`)
	 * @throws \WpOrg\Requests\Exception Prepared string longer than 64 ASCII characters (`idna.prepared_too_long`)
	 * @throws \WpOrg\Requests\Exception Provided string already begins with xn-- (`idna.provided_is_prefixed`)
	 * @throws \WpOrg\Requests\Exception Encoded string longer than 64 ASCII characters (`idna.encoded_too_long`)
	 */
	public static function to_ascii($text) {
		// Step 1: Check if the text is already ASCII
		if (self::is_ascii($text)) {
			// Skip to step 7
			if (strlen($text) < self::MAX_LENGTH) {
				return $text;
			}

			throw new Exception('Provided string is too long', 'idna.provided_too_long', $text);
		}

		// Step 2: nameprep
		$text = self::nameprep($text);

		// Step 3: UseSTD3ASCIIRules is false, continue
		// Step 4: Check if it's ASCII now
		if (self::is_ascii($text)) {
			// Skip to step 7
			/*
			 * As the `nameprep()` method returns the original string, this code will never be reached until
			 * that method is properly implemented.
			 */
			// @codeCoverageIgnoreStart
			if (strlen($text) < self::MAX_LENGTH) {
				return $text;
			}

			throw new Exception('Prepared string is too long', 'idna.prepared_too_long', $text);
			// @codeCoverageIgnoreEnd
		}

		// Step 5: Check ACE prefix
		if (strpos($text, self::ACE_PREFIX) === 0) {
			throw new Exception('Provided string begins with ACE prefix', 'idna.provided_is_prefixed', $text);
		}

		// Step 6: Encode with Punycode
		$text = self::punycode_encode($text);

		// Step 7: Prepend ACE prefix
		$text = self::ACE_PREFIX . $text;

		// Step 8: Check size
		if (strlen($text) < self::MAX_LENGTH) {
			return $text;
		}

		throw new Exception('Encoded string is too long', 'idna.encoded_too_long', $text);
	}

	/**
	 * Check whether a given text string contains only ASCII characters
	 *
	 * @internal (Testing found regex was the fastest implementation)
	 *
	 * @param string $text Text to examine.
	 * @return bool Is the text string ASCII-only?
	 */
	protected static function is_ascii($text) {
		return (preg_match('/(?:[^\x00-\x7F])/', $text) !== 1);
	}

	/**
	 * Prepare a text string for use as an IDNA name
	 *
	 * @todo Implement this based on RFC 3491 and the newer 5891
	 * @param string $text Text to prepare.
	 * @return string Prepared string
	 */
	protected static function nameprep($text) {
		return $text;
	}

	/**
	 * Convert a UTF-8 string to a UCS-4 codepoint array
	 *
	 * Based on \WpOrg\Requests\Iri::replace_invalid_with_pct_encoding()
	 *
	 * @param string $input Text to convert.
	 * @return array Unicode code points
	 *
	 * @throws \WpOrg\Requests\Exception Invalid UTF-8 codepoint (`idna.invalidcodepoint`)
	 */
	protected static function utf8_to_codepoints($input) {
		$codepoints = [];

		// Get number of bytes
		$strlen = strlen($input);

		// phpcs:ignore Generic.CodeAnalysis.JumbledIncrementer -- This is a deliberate choice.
		for ($position = 0; $position < $strlen; $position++) {
			$value = ord($input[$position]);

			if ((~$value & 0x80) === 0x80) {            // One byte sequence:
				$character = $value;
				$length    = 1;
				$remaining = 0;
			} elseif (($value & 0xE0) === 0xC0) {       // Two byte sequence:
				$character = ($value & 0x1F) << 6;
				$length    = 2;
				$remaining = 1;
			} elseif (($value & 0xF0) === 0xE0) {       // Three byte sequence:
				$character = ($value & 0x0F) << 12;
				$length    = 3;
				$remaining = 2;
			} elseif (($value & 0xF8) === 0xF0) {       // Four byte sequence:
				$character = ($value & 0x07) << 18;
				$length    = 4;
				$remaining = 3;
			} else {                                    // Invalid byte:
				throw new Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $value);
			}

			if ($remaining > 0) {
				if ($position + $length > $strlen) {
					throw new Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character);
				}

				for ($position++; $remaining > 0; $position++) {
					$value = ord($input[$position]);

					// If it is invalid, count the sequence as invalid and reprocess the current byte:
					if (($value & 0xC0) !== 0x80) {
						throw new Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character);
					}

					--$remaining;
					$character |= ($value & 0x3F) << ($remaining * 6);
				}

				$position--;
			}

			if (// Non-shortest form sequences are invalid
				$length > 1 && $character <= 0x7F
				|| $length > 2 && $character <= 0x7FF
				|| $length > 3 && $character <= 0xFFFF
				// Outside of range of ucschar codepoints
				// Noncharacters
				|| ($character & 0xFFFE) === 0xFFFE
				|| $character >= 0xFDD0 && $character <= 0xFDEF
				|| (
					// Everything else not in ucschar
					$character > 0xD7FF && $character < 0xF900
					|| $character < 0x20
					|| $character > 0x7E && $character < 0xA0
					|| $character > 0xEFFFD
				)
			) {
				throw new Exception('Invalid Unicode codepoint', 'idna.invalidcodepoint', $character);
			}

			$codepoints[] = $character;
		}

		return $codepoints;
	}

	/**
	 * RFC3492-compliant encoder
	 *
	 * @internal Pseudo-code from Section 6.3 is commented with "#" next to relevant code
	 *
	 * @param string $input UTF-8 encoded string to encode
	 * @return string Punycode-encoded string
	 *
	 * @throws \WpOrg\Requests\Exception On character outside of the domain (never happens with Punycode) (`idna.character_outside_domain`)
	 */
	public static function punycode_encode($input) {
		$output = '';
		// let n = initial_n
		$n = self::BOOTSTRAP_INITIAL_N;
		// let delta = 0
		$delta = 0;
		// let bias = initial_bias
		$bias = self::BOOTSTRAP_INITIAL_BIAS;
		// let h = b = the number of basic code points in the input
		$h = 0;
		$b = 0; // see loop
		// copy them to the output in order
		$codepoints = self::utf8_to_codepoints($input);
		$extended   = [];

		foreach ($codepoints as $char) {
			if ($char < 128) {
				// Character is valid ASCII
				// TODO: this should also check if it's valid for a URL
				$output .= chr($char);
				$h++;

				// Check if the character is non-ASCII, but below initial n
				// This never occurs for Punycode, so ignore in coverage
				// @codeCoverageIgnoreStart
			} elseif ($char < $n) {
				throw new Exception('Invalid character', 'idna.character_outside_domain', $char);
				// @codeCoverageIgnoreEnd
			} else {
				$extended[$char] = true;
			}
		}

		$extended = array_keys($extended);
		sort($extended);
		$b = $h;
		// [copy them] followed by a delimiter if b > 0
		if (strlen($output) > 0) {
			$output .= '-';
		}

		// {if the input contains a non-basic code point < n then fail}
		// while h < length(input) do begin
		$codepointcount = count($codepoints);
		while ($h < $codepointcount) {
			// let m = the minimum code point >= n in the input
			$m = array_shift($extended);
			//printf('next code point to insert is %s' . PHP_EOL, dechex($m));
			// let delta = delta + (m - n) * (h + 1), fail on overflow
			$delta += ($m - $n) * ($h + 1);
			// let n = m
			$n = $m;
			// for each code point c in the input (in order) do begin
			for ($num = 0; $num < $codepointcount; $num++) {
				$c = $codepoints[$num];
				// if c < n then increment delta, fail on overflow
				if ($c < $n) {
					$delta++;
				} elseif ($c === $n) { // if c == n then begin
					// let q = delta
					$q = $delta;
					// for k = base to infinity in steps of base do begin
					for ($k = self::BOOTSTRAP_BASE; ; $k += self::BOOTSTRAP_BASE) {
						// let t = tmin if k <= bias {+ tmin}, or
						//     tmax if k >= bias + tmax, or k - bias otherwise
						if ($k <= ($bias + self::BOOTSTRAP_TMIN)) {
							$t = self::BOOTSTRAP_TMIN;
						} elseif ($k >= ($bias + self::BOOTSTRAP_TMAX)) {
							$t = self::BOOTSTRAP_TMAX;
						} else {
							$t = $k - $bias;
						}

						// if q < t then break
						if ($q < $t) {
							break;
						}

						// output the code point for digit t + ((q - t) mod (base - t))
						$digit   = (int) ($t + (($q - $t) % (self::BOOTSTRAP_BASE - $t)));
						$output .= self::digit_to_char($digit);
						// let q = (q - t) div (base - t)
						$q = (int) floor(($q - $t) / (self::BOOTSTRAP_BASE - $t));
					} // end
					// output the code point for digit q
					$output .= self::digit_to_char($q);
					// let bias = adapt(delta, h + 1, test h equals b?)
					$bias = self::adapt($delta, $h + 1, $h === $b);
					// let delta = 0
					$delta = 0;
					// increment h
					$h++;
				} // end
			} // end
			// increment delta and n
			$delta++;
			$n++;
		} // end

		return $output;
	}

	/**
	 * Convert a digit to its respective character
	 *
	 * @link https://tools.ietf.org/html/rfc3492#section-5
	 *
	 * @param int $digit Digit in the range 0-35
	 * @return string Single character corresponding to digit
	 *
	 * @throws \WpOrg\Requests\Exception On invalid digit (`idna.invalid_digit`)
	 */
	protected static function digit_to_char($digit) {
		// @codeCoverageIgnoreStart
		// As far as I know, this never happens, but still good to be sure.
		if ($digit < 0 || $digit > 35) {
			throw new Exception(sprintf('Invalid digit %d', $digit), 'idna.invalid_digit', $digit);
		}

		// @codeCoverageIgnoreEnd
		$digits = 'abcdefghijklmnopqrstuvwxyz0123456789';
		return substr($digits, $digit, 1);
	}

	/**
	 * Adapt the bias
	 *
	 * @link https://tools.ietf.org/html/rfc3492#section-6.1
	 * @param int $delta
	 * @param int $numpoints
	 * @param bool $firsttime
	 * @return int|float New bias
	 *
	 * function adapt(delta,numpoints,firsttime):
	 */
	protected static function adapt($delta, $numpoints, $firsttime) {
		// if firsttime then let delta = delta div damp
		if ($firsttime) {
			$delta = floor($delta / self::BOOTSTRAP_DAMP);
		} else {
			// else let delta = delta div 2
			$delta = floor($delta / 2);
		}

		// let delta = delta + (delta div numpoints)
		$delta += floor($delta / $numpoints);
		// let k = 0
		$k = 0;
		// while delta > ((base - tmin) * tmax) div 2 do begin
		$max = floor(((self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN) * self::BOOTSTRAP_TMAX) / 2);
		while ($delta > $max) {
			// let delta = delta div (base - tmin)
			$delta = floor($delta / (self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN));
			// let k = k + base
			$k += self::BOOTSTRAP_BASE;
		} // end
		// return k + (((base - tmin + 1) * delta) div (delta + skew))
		return $k + floor(((self::BOOTSTRAP_BASE - self::BOOTSTRAP_TMIN + 1) * $delta) / ($delta + self::BOOTSTRAP_SKEW));
	}
}
PK��]��y���	Hooks.phpnu�[���<?php
/**
 * Handles adding and dispatching events
 *
 * @package Requests\EventDispatcher
 */

namespace WpOrg\Requests;

use WpOrg\Requests\Exception\InvalidArgument;
use WpOrg\Requests\HookManager;
use WpOrg\Requests\Utility\InputValidator;

/**
 * Handles adding and dispatching events
 *
 * @package Requests\EventDispatcher
 */
class Hooks implements HookManager {
	/**
	 * Registered callbacks for each hook
	 *
	 * @var array
	 */
	protected $hooks = [];

	/**
	 * Register a callback for a hook
	 *
	 * @param string $hook Hook name
	 * @param callable $callback Function/method to call on event
	 * @param int $priority Priority number. <0 is executed earlier, >0 is executed later
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $hook argument is not a string.
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $callback argument is not callable.
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $priority argument is not an integer.
	 */
	public function register($hook, $callback, $priority = 0) {
		if (is_string($hook) === false) {
			throw InvalidArgument::create(1, '$hook', 'string', gettype($hook));
		}

		if (is_callable($callback) === false) {
			throw InvalidArgument::create(2, '$callback', 'callable', gettype($callback));
		}

		if (InputValidator::is_numeric_array_key($priority) === false) {
			throw InvalidArgument::create(3, '$priority', 'integer', gettype($priority));
		}

		if (!isset($this->hooks[$hook])) {
			$this->hooks[$hook] = [
				$priority => [],
			];
		} elseif (!isset($this->hooks[$hook][$priority])) {
			$this->hooks[$hook][$priority] = [];
		}

		$this->hooks[$hook][$priority][] = $callback;
	}

	/**
	 * Dispatch a message
	 *
	 * @param string $hook Hook name
	 * @param array $parameters Parameters to pass to callbacks
	 * @return boolean Successfulness
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $hook argument is not a string.
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $parameters argument is not an array.
	 */
	public function dispatch($hook, $parameters = []) {
		if (is_string($hook) === false) {
			throw InvalidArgument::create(1, '$hook', 'string', gettype($hook));
		}

		// Check strictly against array, as Array* objects don't work in combination with `call_user_func_array()`.
		if (is_array($parameters) === false) {
			throw InvalidArgument::create(2, '$parameters', 'array', gettype($parameters));
		}

		if (empty($this->hooks[$hook])) {
			return false;
		}

		if (!empty($parameters)) {
			// Strip potential keys from the array to prevent them being interpreted as parameter names in PHP 8.0.
			$parameters = array_values($parameters);
		}

		ksort($this->hooks[$hook]);

		foreach ($this->hooks[$hook] as $priority => $hooked) {
			foreach ($hooked as $callback) {
				$callback(...$parameters);
			}
		}

		return true;
	}

	public function __wakeup() {
		throw new \LogicException( __CLASS__ . ' should never be unserialized' );
	}
}
PK��]/�yQ�#�#Session.phpnu�[���<?php
/**
 * Session handler for persistent requests and default parameters
 *
 * @package Requests\SessionHandler
 */

namespace WpOrg\Requests;

use WpOrg\Requests\Cookie\Jar;
use WpOrg\Requests\Exception\InvalidArgument;
use WpOrg\Requests\Iri;
use WpOrg\Requests\Requests;
use WpOrg\Requests\Utility\InputValidator;

/**
 * Session handler for persistent requests and default parameters
 *
 * Allows various options to be set as default values, and merges both the
 * options and URL properties together. A base URL can be set for all requests,
 * with all subrequests resolved from this. Base options can be set (including
 * a shared cookie jar), then overridden for individual requests.
 *
 * @package Requests\SessionHandler
 */
class Session {
	/**
	 * Base URL for requests
	 *
	 * URLs will be made absolute using this as the base
	 *
	 * @var string|null
	 */
	public $url = null;

	/**
	 * Base headers for requests
	 *
	 * @var array
	 */
	public $headers = [];

	/**
	 * Base data for requests
	 *
	 * If both the base data and the per-request data are arrays, the data will
	 * be merged before sending the request.
	 *
	 * @var array
	 */
	public $data = [];

	/**
	 * Base options for requests
	 *
	 * The base options are merged with the per-request data for each request.
	 * The only default option is a shared cookie jar between requests.
	 *
	 * Values here can also be set directly via properties on the Session
	 * object, e.g. `$session->useragent = 'X';`
	 *
	 * @var array
	 */
	public $options = [];

	/**
	 * Create a new session
	 *
	 * @param string|Stringable|null $url Base URL for requests
	 * @param array $headers Default headers for requests
	 * @param array $data Default data for requests
	 * @param array $options Default options for requests
	 *
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $url argument is not a string, Stringable or null.
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $headers argument is not an array.
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $data argument is not an array.
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array.
	 */
	public function __construct($url = null, $headers = [], $data = [], $options = []) {
		if ($url !== null && InputValidator::is_string_or_stringable($url) === false) {
			throw InvalidArgument::create(1, '$url', 'string|Stringable|null', gettype($url));
		}

		if (is_array($headers) === false) {
			throw InvalidArgument::create(2, '$headers', 'array', gettype($headers));
		}

		if (is_array($data) === false) {
			throw InvalidArgument::create(3, '$data', 'array', gettype($data));
		}

		if (is_array($options) === false) {
			throw InvalidArgument::create(4, '$options', 'array', gettype($options));
		}

		$this->url     = $url;
		$this->headers = $headers;
		$this->data    = $data;
		$this->options = $options;

		if (empty($this->options['cookies'])) {
			$this->options['cookies'] = new Jar();
		}
	}

	/**
	 * Get a property's value
	 *
	 * @param string $name Property name.
	 * @return mixed|null Property value, null if none found
	 */
	public function __get($name) {
		if (isset($this->options[$name])) {
			return $this->options[$name];
		}

		return null;
	}

	/**
	 * Set a property's value
	 *
	 * @param string $name Property name.
	 * @param mixed $value Property value
	 */
	public function __set($name, $value) {
		$this->options[$name] = $value;
	}

	/**
	 * Remove a property's value
	 *
	 * @param string $name Property name.
	 */
	public function __isset($name) {
		return isset($this->options[$name]);
	}

	/**
	 * Remove a property's value
	 *
	 * @param string $name Property name.
	 */
	public function __unset($name) {
		unset($this->options[$name]);
	}

	/**#@+
	 * @see \WpOrg\Requests\Session::request()
	 * @param string $url
	 * @param array $headers
	 * @param array $options
	 * @return \WpOrg\Requests\Response
	 */
	/**
	 * Send a GET request
	 */
	public function get($url, $headers = [], $options = []) {
		return $this->request($url, $headers, null, Requests::GET, $options);
	}

	/**
	 * Send a HEAD request
	 */
	public function head($url, $headers = [], $options = []) {
		return $this->request($url, $headers, null, Requests::HEAD, $options);
	}

	/**
	 * Send a DELETE request
	 */
	public function delete($url, $headers = [], $options = []) {
		return $this->request($url, $headers, null, Requests::DELETE, $options);
	}
	/**#@-*/

	/**#@+
	 * @see \WpOrg\Requests\Session::request()
	 * @param string $url
	 * @param array $headers
	 * @param array $data
	 * @param array $options
	 * @return \WpOrg\Requests\Response
	 */
	/**
	 * Send a POST request
	 */
	public function post($url, $headers = [], $data = [], $options = []) {
		return $this->request($url, $headers, $data, Requests::POST, $options);
	}

	/**
	 * Send a PUT request
	 */
	public function put($url, $headers = [], $data = [], $options = []) {
		return $this->request($url, $headers, $data, Requests::PUT, $options);
	}

	/**
	 * Send a PATCH request
	 *
	 * Note: Unlike {@see \WpOrg\Requests\Session::post()} and {@see \WpOrg\Requests\Session::put()},
	 * `$headers` is required, as the specification recommends that should send an ETag
	 *
	 * @link https://tools.ietf.org/html/rfc5789
	 */
	public function patch($url, $headers, $data = [], $options = []) {
		return $this->request($url, $headers, $data, Requests::PATCH, $options);
	}
	/**#@-*/

	/**
	 * Main interface for HTTP requests
	 *
	 * This method initiates a request and sends it via a transport before
	 * parsing.
	 *
	 * @see \WpOrg\Requests\Requests::request()
	 *
	 * @param string $url URL to request
	 * @param array $headers Extra headers to send with the request
	 * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests
	 * @param string $type HTTP request type (use \WpOrg\Requests\Requests constants)
	 * @param array $options Options for the request (see {@see \WpOrg\Requests\Requests::request()})
	 * @return \WpOrg\Requests\Response
	 *
	 * @throws \WpOrg\Requests\Exception On invalid URLs (`nonhttp`)
	 */
	public function request($url, $headers = [], $data = [], $type = Requests::GET, $options = []) {
		$request = $this->merge_request(compact('url', 'headers', 'data', 'options'));

		return Requests::request($request['url'], $request['headers'], $request['data'], $type, $request['options']);
	}

	/**
	 * Send multiple HTTP requests simultaneously
	 *
	 * @see \WpOrg\Requests\Requests::request_multiple()
	 *
	 * @param array $requests Requests data (see {@see \WpOrg\Requests\Requests::request_multiple()})
	 * @param array $options Global and default options (see {@see \WpOrg\Requests\Requests::request()})
	 * @return array Responses (either \WpOrg\Requests\Response or a \WpOrg\Requests\Exception object)
	 *
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $requests argument is not an array or iterable object with array access.
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $options argument is not an array.
	 */
	public function request_multiple($requests, $options = []) {
		if (InputValidator::has_array_access($requests) === false || InputValidator::is_iterable($requests) === false) {
			throw InvalidArgument::create(1, '$requests', 'array|ArrayAccess&Traversable', gettype($requests));
		}

		if (is_array($options) === false) {
			throw InvalidArgument::create(2, '$options', 'array', gettype($options));
		}

		foreach ($requests as $key => $request) {
			$requests[$key] = $this->merge_request($request, false);
		}

		$options = array_merge($this->options, $options);

		// Disallow forcing the type, as that's a per request setting
		unset($options['type']);

		return Requests::request_multiple($requests, $options);
	}

	public function __wakeup() {
		throw new \LogicException( __CLASS__ . ' should never be unserialized' );
	}

	/**
	 * Merge a request's data with the default data
	 *
	 * @param array $request Request data (same form as {@see \WpOrg\Requests\Session::request_multiple()})
	 * @param boolean $merge_options Should we merge options as well?
	 * @return array Request data
	 */
	protected function merge_request($request, $merge_options = true) {
		if ($this->url !== null) {
			$request['url'] = Iri::absolutize($this->url, $request['url']);
			$request['url'] = $request['url']->uri;
		}

		if (empty($request['headers'])) {
			$request['headers'] = [];
		}

		$request['headers'] = array_merge($this->headers, $request['headers']);

		if (empty($request['data'])) {
			if (is_array($this->data)) {
				$request['data'] = $this->data;
			}
		} elseif (is_array($request['data']) && is_array($this->data)) {
			$request['data'] = array_merge($this->data, $request['data']);
		}

		if ($merge_options === true) {
			$request['options'] = array_merge($this->options, $request['options']);

			// Disallow forcing the type, as that's a per request setting
			unset($request['options']['type']);
		}

		return $request;
	}
}
PK��]^���Response/Headers.phpnu�[���<?php
/**
 * Case-insensitive dictionary, suitable for HTTP headers
 *
 * @package Requests
 */

namespace WpOrg\Requests\Response;

use WpOrg\Requests\Exception;
use WpOrg\Requests\Exception\InvalidArgument;
use WpOrg\Requests\Utility\CaseInsensitiveDictionary;
use WpOrg\Requests\Utility\FilteredIterator;

/**
 * Case-insensitive dictionary, suitable for HTTP headers
 *
 * @package Requests
 */
class Headers extends CaseInsensitiveDictionary {
	/**
	 * Get the given header
	 *
	 * Unlike {@see \WpOrg\Requests\Response\Headers::getValues()}, this returns a string. If there are
	 * multiple values, it concatenates them with a comma as per RFC2616.
	 *
	 * Avoid using this where commas may be used unquoted in values, such as
	 * Set-Cookie headers.
	 *
	 * @param string $offset Name of the header to retrieve.
	 * @return string|null Header value
	 */
	public function offsetGet($offset) {
		if (is_string($offset)) {
			$offset = strtolower($offset);
		}

		if (!isset($this->data[$offset])) {
			return null;
		}

		return $this->flatten($this->data[$offset]);
	}

	/**
	 * Set the given item
	 *
	 * @param string $offset Item name
	 * @param string $value Item value
	 *
	 * @throws \WpOrg\Requests\Exception On attempting to use dictionary as list (`invalidset`)
	 */
	public function offsetSet($offset, $value) {
		if ($offset === null) {
			throw new Exception('Object is a dictionary, not a list', 'invalidset');
		}

		if (is_string($offset)) {
			$offset = strtolower($offset);
		}

		if (!isset($this->data[$offset])) {
			$this->data[$offset] = [];
		}

		$this->data[$offset][] = $value;
	}

	/**
	 * Get all values for a given header
	 *
	 * @param string $offset Name of the header to retrieve.
	 * @return array|null Header values
	 *
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not valid as an array key.
	 */
	public function getValues($offset) {
		if (!is_string($offset) && !is_int($offset)) {
			throw InvalidArgument::create(1, '$offset', 'string|int', gettype($offset));
		}

		if (is_string($offset)) {
			$offset = strtolower($offset);
		}

		if (!isset($this->data[$offset])) {
			return null;
		}

		return $this->data[$offset];
	}

	/**
	 * Flattens a value into a string
	 *
	 * Converts an array into a string by imploding values with a comma, as per
	 * RFC2616's rules for folding headers.
	 *
	 * @param string|array $value Value to flatten
	 * @return string Flattened value
	 *
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string or an array.
	 */
	public function flatten($value) {
		if (is_string($value)) {
			return $value;
		}

		if (is_array($value)) {
			return implode(',', $value);
		}

		throw InvalidArgument::create(1, '$value', 'string|array', gettype($value));
	}

	/**
	 * Get an iterator for the data
	 *
	 * Converts the internally stored values to a comma-separated string if there is more
	 * than one value for a key.
	 *
	 * @return \ArrayIterator
	 */
	public function getIterator() {
		return new FilteredIterator($this->data, [$this, 'flatten']);
	}
}
PK��]w	׺11Ssl.phpnu�[���<?php
/**
 * SSL utilities for Requests
 *
 * @package Requests\Utilities
 */

namespace WpOrg\Requests;

use WpOrg\Requests\Exception\InvalidArgument;
use WpOrg\Requests\Utility\InputValidator;

/**
 * SSL utilities for Requests
 *
 * Collection of utilities for working with and verifying SSL certificates.
 *
 * @package Requests\Utilities
 */
final class Ssl {
	/**
	 * Verify the certificate against common name and subject alternative names
	 *
	 * Unfortunately, PHP doesn't check the certificate against the alternative
	 * names, leading things like 'https://www.github.com/' to be invalid.
	 *
	 * @link https://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1
	 *
	 * @param string|Stringable $host Host name to verify against
	 * @param array $cert Certificate data from openssl_x509_parse()
	 * @return bool
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $host argument is not a string or a stringable object.
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $cert argument is not an array or array accessible.
	 */
	public static function verify_certificate($host, $cert) {
		if (InputValidator::is_string_or_stringable($host) === false) {
			throw InvalidArgument::create(1, '$host', 'string|Stringable', gettype($host));
		}

		if (InputValidator::has_array_access($cert) === false) {
			throw InvalidArgument::create(2, '$cert', 'array|ArrayAccess', gettype($cert));
		}

		$has_dns_alt = false;

		// Check the subjectAltName
		if (!empty($cert['extensions']['subjectAltName'])) {
			$altnames = explode(',', $cert['extensions']['subjectAltName']);
			foreach ($altnames as $altname) {
				$altname = trim($altname);
				if (strpos($altname, 'DNS:') !== 0) {
					continue;
				}

				$has_dns_alt = true;

				// Strip the 'DNS:' prefix and trim whitespace
				$altname = trim(substr($altname, 4));

				// Check for a match
				if (self::match_domain($host, $altname) === true) {
					return true;
				}
			}

			if ($has_dns_alt === true) {
				return false;
			}
		}

		// Fall back to checking the common name if we didn't get any dNSName
		// alt names, as per RFC2818
		if (!empty($cert['subject']['CN'])) {
			// Check for a match
			return (self::match_domain($host, $cert['subject']['CN']) === true);
		}

		return false;
	}

	/**
	 * Verify that a reference name is valid
	 *
	 * Verifies a dNSName for HTTPS usage, (almost) as per Firefox's rules:
	 * - Wildcards can only occur in a name with more than 3 components
	 * - Wildcards can only occur as the last character in the first
	 *   component
	 * - Wildcards may be preceded by additional characters
	 *
	 * We modify these rules to be a bit stricter and only allow the wildcard
	 * character to be the full first component; that is, with the exclusion of
	 * the third rule.
	 *
	 * @param string|Stringable $reference Reference dNSName
	 * @return boolean Is the name valid?
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not a string or a stringable object.
	 */
	public static function verify_reference_name($reference) {
		if (InputValidator::is_string_or_stringable($reference) === false) {
			throw InvalidArgument::create(1, '$reference', 'string|Stringable', gettype($reference));
		}

		if ($reference === '') {
			return false;
		}

		if (preg_match('`\s`', $reference) > 0) {
			// Whitespace detected. This can never be a dNSName.
			return false;
		}

		$parts = explode('.', $reference);
		if ($parts !== array_filter($parts)) {
			// DNSName cannot contain two dots next to each other.
			return false;
		}

		// Check the first part of the name
		$first = array_shift($parts);

		if (strpos($first, '*') !== false) {
			// Check that the wildcard is the full part
			if ($first !== '*') {
				return false;
			}

			// Check that we have at least 3 components (including first)
			if (count($parts) < 2) {
				return false;
			}
		}

		// Check the remaining parts
		foreach ($parts as $part) {
			if (strpos($part, '*') !== false) {
				return false;
			}
		}

		// Nothing found, verified!
		return true;
	}

	/**
	 * Match a hostname against a dNSName reference
	 *
	 * @param string|Stringable $host Requested host
	 * @param string|Stringable $reference dNSName to match against
	 * @return boolean Does the domain match?
	 * @throws \WpOrg\Requests\Exception\InvalidArgument When either of the passed arguments is not a string or a stringable object.
	 */
	public static function match_domain($host, $reference) {
		if (InputValidator::is_string_or_stringable($host) === false) {
			throw InvalidArgument::create(1, '$host', 'string|Stringable', gettype($host));
		}

		// Check if the reference is blocklisted first
		if (self::verify_reference_name($reference) !== true) {
			return false;
		}

		// Check for a direct match
		if ((string) $host === (string) $reference) {
			return true;
		}

		// Calculate the valid wildcard match if the host is not an IP address
		// Also validates that the host has 3 parts or more, as per Firefox's ruleset,
		// as a wildcard reference is only allowed with 3 parts or more, so the
		// comparison will never match if host doesn't contain 3 parts or more as well.
		if (ip2long($host) === false) {
			$parts    = explode('.', $host);
			$parts[0] = '*';
			$wildcard = implode('.', $parts);
			if ($wildcard === (string) $reference) {
				return true;
			}
		}

		return false;
	}
}
PK��]��C��HookManager.phpnu�[���<?php
/**
 * Event dispatcher
 *
 * @package Requests\EventDispatcher
 */

namespace WpOrg\Requests;

/**
 * Event dispatcher
 *
 * @package Requests\EventDispatcher
 */
interface HookManager {
	/**
	 * Register a callback for a hook
	 *
	 * @param string $hook Hook name
	 * @param callable $callback Function/method to call on event
	 * @param int $priority Priority number. <0 is executed earlier, >0 is executed later
	 */
	public function register($hook, $callback, $priority = 0);

	/**
	 * Dispatch a message
	 *
	 * @param string $hook Hook name
	 * @param array $parameters Parameters to pass to callbacks
	 * @return boolean Successfulness
	 */
	public function dispatch($hook, $parameters = []);
}
PK�M]�!<�HHCommand/Inspect.phpnu�[���<?php

namespace Plesk\Wappspector\Command;

use FilesystemIterator;
use JsonException;
use Plesk\Wappspector\MatchResult\MatchResultInterface;
use Plesk\Wappspector\Wappspector;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use SplFileInfo;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Logger\ConsoleLogger;
use Symfony\Component\Console\Output\OutputInterface;
use Throwable;

#[AsCommand(name: 'wappspector:inspect')]
class Inspect extends Command
{
    public function __construct(private Wappspector $wappspector)
    {
        parent::__construct();
        $this->addArgument('path', InputArgument::OPTIONAL, 'Root path', getcwd());
        $this->addOption('json', '', InputOption::VALUE_NONE, 'JSON output');
        $this->addOption('recursive', '', InputOption::VALUE_NEGATABLE, 'Traverse directories recursive', true);
        $this->addOption('depth', '', InputOption::VALUE_OPTIONAL, 'Depth of recurse', 1);
        $this->addOption(
            'max',
            '',
            InputOption::VALUE_REQUIRED,
            'Maximum number of technologies that can be found for directory. Default = 0 (no limit)',
            0
        );
    }

    public function execute(InputInterface $input, OutputInterface $output): int
    {
        $isJson = (bool)$input->getOption('json');
        $logger = new ConsoleLogger($output);
        $result = [];
        $matchersLimit = (int)$input->getOption('max');

        try {
            foreach ($this->getPath($input) as $path) {
                $result = [...$result, ...$this->wappspector->run($path, '/', $matchersLimit)];
            }
            $result = $this->filterResults($result);

            if ($isJson) {
                $this->jsonOutput($output, $result);
                return Command::SUCCESS;
            }

            $this->tableOutput($output, $result);

            return Command::SUCCESS;
        } catch (Throwable $exception) {
            $logger->error($exception->getMessage());
            return Command::FAILURE;
        }
    }

    /**
     * @throws JsonException
     */
    private function jsonOutput(OutputInterface $output, array $result): void
    {
        $output->writeln(json_encode($result, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
    }

    /**
     * @param MatchResultInterface[] $matchers
     * @return void
     */
    private function tableOutput(OutputInterface $output, array $matchers): void
    {
        $rows = [];

        foreach ($matchers as $matchResult) {
            $rows[] = [
                $matchResult->getId(),
                $matchResult->getName(),
                $matchResult->getPath(),
                $matchResult->getVersion() ?? '-',
            ];
        }

        $table = new Table($output);
        $table
            ->setHeaders(['ID', 'Technology', 'Path', 'Version'])
            ->setRows($rows);
        $table->render();
    }

    private function getPath(InputInterface $input): iterable
    {
        $path = $input->getArgument('path');
        $path = realpath($path);
        if (!$input->getOption('recursive')) {
            yield $path;
            return;
        }

        $flags = FilesystemIterator::KEY_AS_PATHNAME
            | FilesystemIterator::CURRENT_AS_FILEINFO
            | FilesystemIterator::SKIP_DOTS;
        $itFlags = RecursiveIteratorIterator::SELF_FIRST;
        $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, $flags), $itFlags);
        $it->setMaxDepth((int)$input->getOption('depth'));

        foreach ($it as $path => $item) {
            /** @var SplFileInfo $item */
            if (str_contains($path, '/.')) {
                continue;
            }
            if (!$item->isDir()) {
                continue;
            }
            yield $path;
        }
    }

    /**
     * @param MatchResultInterface[] $result
     * @return MatchResultInterface[]
     */
    private function filterResults(array $result): array
    {
        return array_values(
            array_filter($result, static function (MatchResultInterface $matcher) {
                static $uniq = [];
                $key = $matcher->getId() . ':' . $matcher->getPath();
                if (array_key_exists($key, $uniq)) {
                    return false;
                }
                $uniq[$key] = true;
                return true;
            })
        );
    }
}
PK�M]�5&��MatchResult/Php.phpnu�[���<?php

declare(strict_types=1);


namespace Plesk\Wappspector\MatchResult;

class Php extends MatchResult
{
    public const ID = 'php';
    public const NAME = 'PHP';
}
PK�M]l~���MatchResult/Prestashop.phpnu�[���<?php

declare(strict_types=1);


namespace Plesk\Wappspector\MatchResult;

class Prestashop extends MatchResult
{
    public const ID = 'prestashop';
    public const NAME = 'PrestaShop';
}
PK�M]JS����MatchResult/Siteplus.phpnu�[���<?php

declare(strict_types=1);


namespace Plesk\Wappspector\MatchResult;

class Siteplus extends MatchResult
{
    public const ID = 'siteplus';
    public const NAME = 'Siteplus';
}
PK�M]��z��MatchResult/Duda.phpnu�[���<?php

declare(strict_types=1);


namespace Plesk\Wappspector\MatchResult;

class Duda extends MatchResult
{
    public const ID = 'duda';
    public const NAME = 'duda.co';
}
PK�M]�����MatchResult/Yii.phpnu�[���<?php

declare(strict_types=1);


namespace Plesk\Wappspector\MatchResult;

class Yii extends MatchResult
{
    public const ID = 'yii';
    public const NAME = 'Yii';
}
PK�M]9��!��MatchResult/Symfony.phpnu�[���<?php

declare(strict_types=1);


namespace Plesk\Wappspector\MatchResult;

class Symfony extends MatchResult
{
    public const ID = 'symfony';
    public const NAME = 'Symfony';
}
PK�M]�����MatchResult/CakePHP.phpnu�[���<?php

declare(strict_types=1);


namespace Plesk\Wappspector\MatchResult;

class CakePHP extends MatchResult
{
    public const ID = 'cakephp';
    public const NAME = 'CakePHP';
}
PK�M]�Q���$MatchResult/MatchResultInterface.phpnu�[���<?php

declare(strict_types=1);


namespace Plesk\Wappspector\MatchResult;

interface MatchResultInterface
{
    /**
     * Internal ID of the technology
     */
    public function getId(): string;

    /**
     * Human-readable technology name
     */
    public function getName(): string;

    public function getPath(): string;

    public function getVersion(): ?string;

    public function getApplication(): ?string;
}
PK�M]�r�ٰ�MatchResult/Typo3.phpnu�[���<?php

declare(strict_types=1);


namespace Plesk\Wappspector\MatchResult;

class Typo3 extends MatchResult
{
    public const ID = 'typo3';
    public const NAME = 'TYPO3';
}
PK�M]��zJ��MatchResult/Sitejet.phpnu�[���<?php

declare(strict_types=1);


namespace Plesk\Wappspector\MatchResult;

class Sitejet extends MatchResult
{
    public const ID = 'sitejet';
    public const NAME = 'Sitejet';
}
PK�M]��w� MatchResult/EmptyMatchResult.phpnu�[���<?php

declare(strict_types=1);


namespace Plesk\Wappspector\MatchResult;

class EmptyMatchResult implements MatchResultInterface
{
    public function getId(): string
    {
        return 'unknown';
    }

    public function getName(): string
    {
        return 'Unknown';
    }

    public function getPath(): string
    {
        return '';
    }

    public function getVersion(): ?string
    {
        return null;
    }

    public function getApplication(): ?string
    {
        return null;
    }
}
PK�M]�Ҧ��MatchResult/EmDash.phpnu�[���<?php

declare(strict_types=1);
namespace Plesk\Wappspector\MatchResult;

class EmDash extends MatchResult
{
    public const ID = 'emdash';
    public const NAME = 'EmDash';
}
PK�M]��MatchResult/NodeJs.phpnu�[���<?php

declare(strict_types=1);


namespace Plesk\Wappspector\MatchResult;

class NodeJs extends MatchResult
{
    public const ID = 'nodejs';
    public const NAME = 'Node.js';
}
PK�M]�-"U��MatchResult/Python.phpnu�[���<?php

declare(strict_types=1);


namespace Plesk\Wappspector\MatchResult;

class Python extends MatchResult
{
    public const ID = 'python';
    public const NAME = 'Python';
}
PK�M]������MatchResult/CodeIgniter.phpnu�[���<?php

declare(strict_types=1);


namespace Plesk\Wappspector\MatchResult;

class CodeIgniter extends MatchResult
{
    public const ID = 'codeigniter';
    public const NAME = 'CodeIgniter';
}
PK�M]�>��MatchResult/Composer.phpnu�[���<?php

declare(strict_types=1);


namespace Plesk\Wappspector\MatchResult;

class Composer extends MatchResult
{
    public const ID = 'composer';
    public const NAME = 'Composer';
}
PK�M]�䟥��MatchResult/Sitepro.phpnu�[���<?php

declare(strict_types=1);


namespace Plesk\Wappspector\MatchResult;

class Sitepro extends MatchResult
{
    public const ID = 'sitepro';
    public const NAME = 'Site.pro';
}
PK�M]	{��MatchResult/Laravel.phpnu�[���<?php

declare(strict_types=1);


namespace Plesk\Wappspector\MatchResult;

class Laravel extends MatchResult
{
    public const ID = 'laravel';
    public const NAME = 'Laravel';
}
PK�M].�
�
�
MatchResult/MatchResult.phpnu�[���<?php

declare(strict_types=1);


namespace Plesk\Wappspector\MatchResult;

use JsonSerializable;
use League\Flysystem\PathTraversalDetected;
use League\Flysystem\WhitespacePathNormalizer;

class MatchResult implements MatchResultInterface, JsonSerializable
{
    public const ID = null;
    public const NAME = null;

    public function __construct(
        protected string $path,
        protected ?string $version = null,
        protected ?string $application = null,
    ) {
        try {
            $this->path = (new WhitespacePathNormalizer())->normalizePath($this->path);
        } catch (PathTraversalDetected) {
            $this->path = '/';
        }
    }

    public function getId(): string
    {
        return static::ID;
    }

    public function getName(): string
    {
        return static::NAME;
    }

    public function getPath(): string
    {
        return $this->path;
    }

    public function getVersion(): ?string
    {
        return $this->version;
    }

    public function getApplication(): ?string
    {
        return $this->application;
    }

    public function jsonSerialize(): array
    {
        return [
            'id' => $this->getId(),
            'name' => $this->getName(),
            'path' => $this->getPath(),
            'version' => $this->getVersion(),
            'application' => $this->getApplication(),
        ];
    }

    public static function createById(
        string $id,
        ?string $path = null,
        ?string $version = null,
        ?string $application = null
    ): MatchResultInterface {
        $classname = match ($id) {
            CakePHP::ID => CakePHP::class,
            CodeIgniter::ID => CodeIgniter::class,
            Composer::ID => Composer::class,
            DotNet::ID => DotNet::class,
            Drupal::ID => Drupal::class,
            Joomla::ID => Joomla::class,
            Laravel::ID => Laravel::class,
            EmDash::ID => EmDash::class,
            NodeJs::ID => NodeJs::class,
            Php::ID => Php::class,
            Prestashop::ID => Prestashop::class,
            Python::ID => Python::class,
            Ruby::ID => Ruby::class,
            Symfony::ID => Symfony::class,
            Typo3::ID => Typo3::class,
            Wordpress::ID => Wordpress::class,
            Yii::ID => Yii::class,
            Sitejet::ID => Sitejet::class,
            WebPresenceBuilder::ID => WebPresenceBuilder::class,
            Sitepro::ID => Sitepro::class,
            Duda::ID => Duda::class,
            Siteplus::ID => Siteplus::class,
            default => null,
        };

        if (!$classname) {
            return new EmptyMatchResult();
        }

        return new $classname(path: $path ?? '', version: $version, application: $application);
    }
}
PK�M]�͙K��MatchResult/Ruby.phpnu�[���<?php

declare(strict_types=1);


namespace Plesk\Wappspector\MatchResult;

class Ruby extends MatchResult
{
    public const ID = 'ruby';
    public const NAME = 'Ruby';
}
PK�M]Ȼ'��MatchResult/Wordpress.phpnu�[���<?php

declare(strict_types=1);


namespace Plesk\Wappspector\MatchResult;

class Wordpress extends MatchResult
{
    public const ID = 'wordpress';
    public const NAME = 'WordPress';
}
PK�M]r�,��MatchResult/Drupal.phpnu�[���<?php

declare(strict_types=1);


namespace Plesk\Wappspector\MatchResult;

class Drupal extends MatchResult
{
    public const ID = 'drupal';
    public const NAME = 'Drupal';
}
PK�M]��?��MatchResult/DotNet.phpnu�[���<?php

declare(strict_types=1);


namespace Plesk\Wappspector\MatchResult;

class DotNet extends MatchResult
{
    public const ID = 'dotnet';
    public const NAME = '.NET';
}
PK�M]Ǘ�ִ�MatchResult/Joomla.phpnu�[���<?php

declare(strict_types=1);


namespace Plesk\Wappspector\MatchResult;

class Joomla extends MatchResult
{
    public const ID = 'joomla';
    public const NAME = 'Joomla!';
}
PK�M]wC�|��"MatchResult/WebPresenceBuilder.phpnu�[���<?php

declare(strict_types=1);


namespace Plesk\Wappspector\MatchResult;

class WebPresenceBuilder extends MatchResult
{
    public const ID = 'webpresencebuilder';
    public const NAME = 'WebPresenceBuilder';
}
PK�M]]�`%��DIContainer.phpnu�[���<?php

namespace Plesk\Wappspector;

use DI\Container;
use DI\ContainerBuilder;
use Exception;

class DIContainer
{
    /**
     * @throws Exception
     */
    public static function build(): Container
    {
        $containerBuilder = new ContainerBuilder();
        $containerBuilder->addDefinitions(__DIR__ . '/container.php');

        return $containerBuilder->build();
    }
}
PK�M]UMe!HHMatchers/Php.phpnu�[���<?php

namespace Plesk\Wappspector\Matchers;

use League\Flysystem\Filesystem;
use League\Flysystem\FilesystemException;
use League\Flysystem\StorageAttributes;
use Plesk\Wappspector\MatchResult\EmptyMatchResult;
use Plesk\Wappspector\MatchResult\MatchResultInterface;
use Plesk\Wappspector\MatchResult\Php as MatchResult;

class Php implements MatcherInterface
{
    public function match(Filesystem $fs, string $path): MatchResultInterface
    {
        try {
            $list = $fs->listContents($path);
            foreach ($list as $item) {
                /** @var StorageAttributes $item */
                if ($item->isFile() && str_ends_with($item->path(), '.php')) {
                    return new MatchResult($path);
                }

                if ($item->isDir() && $item->path() === ltrim(rtrim($path, '/') . '/src', '/')) {
                    return $this->match($fs, rtrim($path, '/') . '/src');
                }
            }
        } catch (FilesystemException) {
            // skip dir if it is inaccessible
        }

        return new EmptyMatchResult();
    }
}
PK�M]G�qF��Matchers/Prestashop.phpnu�[���<?php

namespace Plesk\Wappspector\Matchers;

use League\Flysystem\Filesystem;
use League\Flysystem\FilesystemException;
use Plesk\Wappspector\MatchResult\EmptyMatchResult;
use Plesk\Wappspector\MatchResult\MatchResultInterface;
use Plesk\Wappspector\MatchResult\Prestashop as MatchResult;

class Prestashop implements MatcherInterface
{
    protected const VERSIONS = [
        [
            'filename' => '/config/settings.inc.php',
            'regexp' => '/define\\(\'_PS_VERSION_\', \'(.+)\'\\)/',
        ],
    ];

    /**
     * @throws FilesystemException
     */
    public function match(Filesystem $fs, string $path): MatchResultInterface
    {
        foreach (self::VERSIONS as $version) {
            $versionFile = rtrim($path, '/') . '/' . $version['filename'];

            if (!$fs->fileExists($versionFile)) {
                continue;
            }

            return new MatchResult($path, $this->getVersion($version, $fs, $versionFile));
        }

        return new EmptyMatchResult();
    }

    public function getVersion(array $version, Filesystem $fs, string $versionFile): ?string
    {
        $result = null;
        try {
            if (preg_match($version['regexp'], $fs->read($versionFile), $matches) && count($matches) > 1) {
                $result = $matches[1];
            }
        } catch (FilesystemException) {
            // ignore filesystem extensions
        }
        return $result;
    }
}
PK�M]��-**Matchers/Siteplus.phpnu�[���<?php

declare(strict_types=1);

namespace Plesk\Wappspector\Matchers;

use League\Flysystem\Filesystem;
use Plesk\Wappspector\Helper\InspectorHelper;
use Plesk\Wappspector\MatchResult\EmptyMatchResult;
use Plesk\Wappspector\MatchResult\MatchResultInterface;
use Plesk\Wappspector\MatchResult\Siteplus as MatchResult;

class Siteplus implements MatcherInterface
{
    private const PUBLISH_DIR_PATH = '/bundle/publish';

    public function match(Filesystem $fs, string $path): MatchResultInterface
    {
        $rTrimPath = rtrim($path, '/');

        $inspectorHelper = new InspectorHelper();

        if (!$inspectorHelper->fileContainsString($fs, $rTrimPath . '/index.html', 'edit.site')) {
            return new EmptyMatchResult();
        }

        if (!$fs->directoryExists($rTrimPath . self::PUBLISH_DIR_PATH)) {
            return new EmptyMatchResult();
        }

        $publishDirList = $fs->listContents($rTrimPath . self::PUBLISH_DIR_PATH, false);

        // do not check if the item is a directory as on the server when the files a copied the type of the
        // directory is determined as 'file'.
        // By default, there should be just 1 directory in the publish directory
        $versionDirPath = $publishDirList->toArray()[0]['path'] ?? null;

        if ($versionDirPath === null) {
            return new EmptyMatchResult();
        }

        return $inspectorHelper->fileContainsString($fs, $versionDirPath . '/bundle.js', 'siteplus')
            ? new MatchResult($rTrimPath, $this->getSiteplusVersion($versionDirPath))
            : new EmptyMatchResult();
    }

    private function getSiteplusVersion(string $versionDirPath): string
    {
        // get the last part of the path
        $versionDirPathParts = explode('/', $versionDirPath);
        return end($versionDirPathParts);
    }
}
PK�M]T�����Matchers/Duda.phpnu�[���<?php

declare(strict_types=1);

namespace Plesk\Wappspector\Matchers;

use League\Flysystem\Filesystem;
use Plesk\Wappspector\Helper\InspectorHelper;
use Plesk\Wappspector\MatchResult\Duda as MatchResult;
use Plesk\Wappspector\MatchResult\EmptyMatchResult;
use Plesk\Wappspector\MatchResult\MatchResultInterface;

class Duda implements MatcherInterface
{
    private const CSS_FILES = [
        '/Style/desktop.css',
        '/Style/mobile.css',
        '/Style/tablet.css',
    ];

    private const RUNTIME_JS_FILE = '/Scripts/runtime.js';

    public function match(Filesystem $fs, string $path): MatchResultInterface
    {
        $rTrimPath = rtrim($path, '/');

        $cssFile = $this->getCssFile($fs, $rTrimPath);

        $inspectorHelper = new InspectorHelper();

        if ($cssFile !== null) {
            $cssFileContent = $fs->read($rTrimPath . $cssFile);
            if (
                $inspectorHelper->fileContentContainsString($cssFileContent, 'dmDudaonePreviewBody') ||
                $inspectorHelper->fileContentContainsString($cssFileContent, 'dudaSnipcartProductGalleryId')
            ) {
                return new MatchResult($path);
            }
        }

        if (!$fs->fileExists($rTrimPath . self::RUNTIME_JS_FILE)) {
            return new EmptyMatchResult();
        }

        return $inspectorHelper->fileContainsString($fs, $rTrimPath . self::RUNTIME_JS_FILE, 'duda')
            ? new MatchResult($path)
            : new EmptyMatchResult();
    }

    private function getCssFile(Filesystem $fs, string $path): ?string
    {
        foreach (self::CSS_FILES as $cssFile) {
            if ($fs->fileExists($path . $cssFile)) {
                return $cssFile;
            }
        }

        return null;
    }
}
PK�M]Kɑ���Matchers/Yii.phpnu�[���<?php

declare(strict_types=1);


namespace Plesk\Wappspector\Matchers;

use League\Flysystem\Filesystem;
use Plesk\Wappspector\MatchResult\EmptyMatchResult;
use Plesk\Wappspector\MatchResult\MatchResultInterface;
use Plesk\Wappspector\MatchResult\Yii as MatchResult;

class Yii implements MatcherInterface
{
    private const VERSIONS = [
        [
            'file' => 'yii',
            'versionFile' => '/vendor/yiisoft/yii2/BaseYii.php',
            'versionRegexp' => '/public static function getVersion\(\)\s*\{\s*return \'([^\']+)\';\s*}/',
        ],
        [
            'file' => 'framework/yiic',
            'versionFile' => '/framework/YiiBase.php',
            'versionRegexp' => '/public static function getVersion\(\)\s*\{\s*return \'([^\']+)\';\s*}/',
        ],
    ];

    public function match(Filesystem $fs, string $path): MatchResultInterface
    {
        $path = rtrim($path, '/');

        foreach (self::VERSIONS as $version) {
            if (!$fs->fileExists($path . '/' . $version['file'])) {
                continue;
            }
            return new MatchResult($path, $this->detectVersion($fs, $path, $version));
        }

        return new EmptyMatchResult();
    }

    private function detectVersion(Filesystem $fs, string $path, array $versionInfo): ?string
    {
        $version = null;

        $yii2VersionFile = $path . $versionInfo['versionFile'];
        if ($fs->fileExists($yii2VersionFile)) {
            preg_match($versionInfo['versionRegexp'], $fs->read($yii2VersionFile), $matches);

            if (isset($matches[1])) {
                $version = $matches[1];
            }
        }

        return $version;
    }
}
PK�M]S[���Matchers/Symfony.phpnu�[���<?php

declare(strict_types=1);


namespace Plesk\Wappspector\Matchers;

use JsonException;
use League\Flysystem\Filesystem;
use Plesk\Wappspector\MatchResult\EmptyMatchResult;
use Plesk\Wappspector\MatchResult\MatchResultInterface;
use Plesk\Wappspector\MatchResult\Symfony as MatchResult;

class Symfony implements MatcherInterface
{
    public function match(Filesystem $fs, string $path): MatchResultInterface
    {
        $symfonyLockFile = rtrim($path, '/') . '/symfony.lock';

        if (!$fs->fileExists($symfonyLockFile)) {
            return new EmptyMatchResult();
        }

        $json = [];
        try {
            $json = json_decode($fs->read($symfonyLockFile), true, 512, JSON_THROW_ON_ERROR);
        } catch (JsonException) {
            // ignore symfony.lock errors
        }

        return new MatchResult($path, $json["symfony/framework-bundle"]["version"] ?? null);
    }
}
PK�M][��Matchers/CakePHP.phpnu�[���<?php

declare(strict_types=1);


namespace Plesk\Wappspector\Matchers;

use League\Flysystem\Filesystem;
use Plesk\Wappspector\MatchResult\CakePHP as MatchResult;
use Plesk\Wappspector\MatchResult\EmptyMatchResult;
use Plesk\Wappspector\MatchResult\MatchResultInterface;

class CakePHP implements MatcherInterface
{
    public function match(Filesystem $fs, string $path): MatchResultInterface
    {
        $path = rtrim($path, '/');
        if (!$fs->fileExists($path . '/bin/cake')) {
            return new EmptyMatchResult();
        }

        $version = $this->detectVersion($fs, $path);

        return new MatchResult($path, $version);
    }

    private function detectVersion(Filesystem $fs, string $path): ?string
    {
        $version = null;

        $versionFile = $path . '/vendor/cakephp/cakephp/VERSION.txt';
        if ($fs->fileExists($versionFile)) {
            $versionData = explode("\n", trim($fs->read($versionFile)));
            $version = trim(array_pop($versionData));
        }

        return $version;
    }
}
PK�M]�1��Matchers/Typo3.phpnu�[���<?php

namespace Plesk\Wappspector\Matchers;

use League\Flysystem\Filesystem;
use League\Flysystem\FilesystemException;
use Plesk\Wappspector\MatchResult\EmptyMatchResult;
use Plesk\Wappspector\MatchResult\MatchResultInterface;
use Plesk\Wappspector\MatchResult\Typo3 as MatchResult;

class Typo3 implements MatcherInterface
{
    /**
     * Version detection information for TYPO3 CMS 4.x and 6.x
     */
    protected const VERSIONS = [
        [
            'filename' => 'typo3/sysext/core/Classes/Information/Typo3Version.php',
            'regexp' => '/VERSION = \'(.*?)\'/',
        ],
        [
            'filename' => 'typo3/sysext/core/Classes/Core/SystemEnvironmentBuilder.php',
            'regexp' => '/define\\(\'TYPO3_version\', \'(.*?)\'\\)/',
        ],
        [
            'filename' => 't3lib/config_default.php',
            'regexp' => '/TYPO_VERSION = \'(.*?)\'/',
        ],
    ];

    public function match(Filesystem $fs, string $path): MatchResultInterface
    {
        foreach (self::VERSIONS as $version) {
            $versionFile = rtrim($path, '/') . '/' . $version['filename'];

            if (!$fs->fileExists($versionFile)) {
                continue;
            }

            if ($version = $this->detectVersion($version['regexp'], $versionFile, $fs)) {
                return new MatchResult($path, $version);
            }
        }

        return new EmptyMatchResult();
    }

    public function detectVersion(string $regexPattern, string $versionFile, Filesystem $fs): ?string
    {
        try {
            preg_match($regexPattern, $fs->read($versionFile), $matches);
            return count($matches) > 1 ? $matches[1] : null;
        } catch (FilesystemException) {
            // ignore file reading problem
            return null;
        }
    }
}
PK�M]=-���Matchers/Sitejet.phpnu�[���<?php

declare(strict_types=1);

namespace Plesk\Wappspector\Matchers;

use League\Flysystem\Filesystem;
use Plesk\Wappspector\Helper\InspectorHelper;
use Plesk\Wappspector\MatchResult\EmptyMatchResult;
use Plesk\Wappspector\MatchResult\MatchResultInterface;
use Plesk\Wappspector\MatchResult\Sitejet as MatchResult;

class Sitejet implements MatcherInterface
{
    public function match(Filesystem $fs, string $path): MatchResultInterface
    {
        $indexHtmlPath = rtrim($path, '/') . '/index.html';
        if (!$fs->fileExists($indexHtmlPath)) {
            return new EmptyMatchResult();
        }

        $fileContent = $fs->read($indexHtmlPath);

        $inspectorHelper = new InspectorHelper();

        return $inspectorHelper->fileContentContainsString($fileContent, 'ed-element')
               && $inspectorHelper->fileContentContainsString($fileContent, 'webcard.apiHost=')
            ? new MatchResult($path)
            : new EmptyMatchResult();
    }
}
PK�M]L���--Matchers/EmDash.phpnu�[���<?php
declare(strict_types=1);
namespace Plesk\Wappspector\Matchers;

use League\Flysystem\Filesystem;
use Plesk\Wappspector\MatchResult\EmDash as MatchResult;
use Plesk\Wappspector\MatchResult\EmptyMatchResult;
use Plesk\Wappspector\MatchResult\MatchResultInterface;

class EmDash implements MatcherInterface
{
    public function match(Filesystem $fs, string $path): MatchResultInterface
    {
        $packageJsonPath = rtrim($path, '/') . '/package.json';
        if (!$fs->fileExists($packageJsonPath)) {
            return new EmptyMatchResult();
        }

        $json = json_decode($fs->read($packageJsonPath), true);

        return is_array($json) && (isset($json['emdash']) || isset($json['dependencies']['emdash']))
            ? new MatchResult($path)
            : new EmptyMatchResult();
    }
}
PK�M]���"bbMatchers/NodeJs.phpnu�[���<?php

declare(strict_types=1);


namespace Plesk\Wappspector\Matchers;

use JsonException;
use League\Flysystem\Filesystem;
use Plesk\Wappspector\MatchResult\EmptyMatchResult;
use Plesk\Wappspector\MatchResult\MatchResultInterface;
use Plesk\Wappspector\MatchResult\NodeJs as MatchResult;

class NodeJs implements MatcherInterface
{
    public function match(Filesystem $fs, string $path): MatchResultInterface
    {
        $packageFile = rtrim($path, '/') . '/package.json';

        if (!$fs->fileExists($packageFile)) {
            return new EmptyMatchResult();
        }

        $json = [];
        try {
            $json = json_decode($fs->read($packageFile), true, 512, JSON_THROW_ON_ERROR);
        } catch (JsonException) {
            // ignore package.json errors
        }

        return new MatchResult($path, null, $json['name'] ?? null);
    }
}
PK�M]�>}��Matchers/Python.phpnu�[���<?php

namespace Plesk\Wappspector\Matchers;

use League\Flysystem\Filesystem;
use League\Flysystem\StorageAttributes;
use Plesk\Wappspector\MatchResult\EmptyMatchResult;
use Plesk\Wappspector\MatchResult\MatchResultInterface;
use Plesk\Wappspector\MatchResult\Python as MatchResult;

class Python implements MatcherInterface
{
    use UpLevelMatcherTrait;

    protected function doMatch(Filesystem $fs, string $path): MatchResultInterface
    {
        foreach ($fs->listContents($path) as $item) {
            /** @var StorageAttributes $item */
            if ($item->isFile() && str_ends_with($item->path(), '.py')) {
                return new MatchResult($path);
            }
        }

        return new EmptyMatchResult();
    }
}
PK�M]�њ8��Matchers/CodeIgniter.phpnu�[���<?php

declare(strict_types=1);


namespace Plesk\Wappspector\Matchers;

use League\Flysystem\Filesystem;
use League\Flysystem\FilesystemException;
use Plesk\Wappspector\MatchResult\CodeIgniter as MatchResult;
use Plesk\Wappspector\MatchResult\EmptyMatchResult;
use Plesk\Wappspector\MatchResult\MatchResultInterface;

class CodeIgniter implements MatcherInterface
{
    public function match(Filesystem $fs, string $path): MatchResultInterface
    {
        $path = rtrim($path, '/');
        if (!$fs->fileExists($path . '/spark')) {
            return new EmptyMatchResult();
        }

        return new MatchResult($path, $this->detectVersion($fs, $path));
    }

    /**
     * @throws FilesystemException
     */
    private function detectVersion(Filesystem $fs, string $path): ?string
    {
        $versionFile = $path . '/vendor/codeigniter4/framework/system/CodeIgniter.php';
        if (!$fs->fileExists($versionFile)) {
            return null;
        }
        preg_match("/CI_VERSION\\s*=\\s*'([^']+)'/", $fs->read($versionFile), $matches);

        if ($matches !== []) {
            return $matches[1];
        }

        return null;
    }
}
PK�M]#�u[[Matchers/Composer.phpnu�[���<?php

namespace Plesk\Wappspector\Matchers;

use JsonException;
use League\Flysystem\Filesystem;
use League\Flysystem\FilesystemException;
use Plesk\Wappspector\MatchResult\Composer as MatchResult;
use Plesk\Wappspector\MatchResult\EmptyMatchResult;
use Plesk\Wappspector\MatchResult\MatchResultInterface;

class Composer implements MatcherInterface
{
    use UpLevelMatcherTrait;

    private function getPath(string $path): string
    {
        return rtrim($path, '/') . '/composer.json';
    }

    /**
     * @throws FilesystemException
     */
    protected function doMatch(Filesystem $fs, string $path): MatchResultInterface
    {
        $composerJsonFile = $this->getPath($path);
        if (!$fs->fileExists($composerJsonFile)) {
            return new EmptyMatchResult();
        }

        $json = [];
        try {
            $json = json_decode($fs->read($composerJsonFile), true, 512, JSON_THROW_ON_ERROR);
        } catch (JsonException) {
            // ignore composer.json errors
        }

        return new MatchResult($path, $json['version'] ?? 'dev', $json['name'] ?? 'unknown');
    }
}
PK�M]����Matchers/Sitepro.phpnu�[���<?php

declare(strict_types=1);

namespace Plesk\Wappspector\Matchers;

use League\Flysystem\Filesystem;
use Plesk\Wappspector\Helper\InspectorHelper;
use Plesk\Wappspector\MatchResult\EmptyMatchResult;
use Plesk\Wappspector\MatchResult\MatchResultInterface;
use Plesk\Wappspector\MatchResult\Sitepro as MatchResult;

class Sitepro implements MatcherInterface
{
    public function match(Filesystem $fs, string $path): MatchResultInterface
    {
        $rTrimPath = rtrim($path, '/');
        $siteproFolderPath =  $rTrimPath . '/sitepro';
        if (!$fs->directoryExists($siteproFolderPath)) {
            return new EmptyMatchResult();
        }

        $inspectorHelper = new InspectorHelper();

        return $inspectorHelper->fileContainsString($fs, $rTrimPath . '/web.config', 'sitepro')
               || $inspectorHelper->fileContainsString($fs, $rTrimPath . '/.htaccess', 'sitepro')
            ? new MatchResult($path)
            : new EmptyMatchResult();
    }
}
PK�M]��K��Matchers/Laravel.phpnu�[���<?php

namespace Plesk\Wappspector\Matchers;

use JsonException;
use League\Flysystem\Filesystem;
use League\Flysystem\FilesystemException;
use Plesk\Wappspector\MatchResult\EmptyMatchResult;
use Plesk\Wappspector\MatchResult\Laravel as MatchResult;
use Plesk\Wappspector\MatchResult\MatchResultInterface;

class Laravel implements MatcherInterface
{
    use UpLevelMatcherTrait;

    private const VERSION_FILE = 'vendor/laravel/framework/src/Illuminate/Foundation/Application.php';
    private const COMPOSER_JSON = 'composer.json';
    private const ARTISAN = 'artisan';

    /**
     * @throws FilesystemException
     */
    protected function doMatch(Filesystem $fs, string $path): MatchResultInterface
    {
        $path = rtrim($path, '/');
        if (!$fs->fileExists($path . '/' . self::ARTISAN)) {
            return new EmptyMatchResult();
        }

        return new MatchResult($path, $this->detectVersion($path, $fs));
    }

    private function detectVersion(string $path, Filesystem $fs): ?string
    {
        $result = null;
        $versionFile = $path . '/' . self::VERSION_FILE;
        if ($fs->fileExists($versionFile)) {
            preg_match("/VERSION\\s*=\\s*'([^']+)'/", $fs->read($versionFile), $matches);
            if ($matches !== []) {
                $result = $matches[1];
            }
        } else {
            $composerJsonFile = $path . '/' . self::COMPOSER_JSON;
            if ($fs->fileExists($composerJsonFile)) {
                try {
                    $json = json_decode($fs->read($composerJsonFile), true, 512, JSON_THROW_ON_ERROR);
                    if ($laravelPackage = $json['require']['laravel/framework'] ?? null) {
                        $result = str_replace('^', '', $laravelPackage);
                    }
                } catch (JsonException) {
                    // ignore composer.json errors
                }
            }
        }

        return $result;
    }
}
PK�M]����� Matchers/UpLevelMatcherTrait.phpnu�[���<?php

namespace Plesk\Wappspector\Matchers;

use League\Flysystem\Filesystem;
use League\Flysystem\FilesystemException;
use Plesk\Wappspector\MatchResult\EmptyMatchResult;
use Plesk\Wappspector\MatchResult\MatchResultInterface;

trait UpLevelMatcherTrait
{
    abstract protected function doMatch(Filesystem $fs, string $path): MatchResultInterface;

    public function match(Filesystem $fs, string $path): MatchResultInterface
    {
        $matcher = $this->safeScanDir($fs, $path);
        if ($matcher instanceof EmptyMatchResult) {
            $matcher = $this->safeScanDir($fs, rtrim($path) . '/../');
        }
        return $matcher;
    }

    private function safeScanDir(Filesystem $fs, string $path): MatchResultInterface
    {
        try {
            $result = $this->doMatch($fs, $path);
        } catch (FilesystemException) {
            // skip dir if it is inaccessible
            $result = new EmptyMatchResult();
        }

        return $result;
    }
}
PK�M]����Matchers/Ruby.phpnu�[���<?php

namespace Plesk\Wappspector\Matchers;

use League\Flysystem\Filesystem;
use League\Flysystem\FilesystemException;
use Plesk\Wappspector\MatchResult\EmptyMatchResult;
use Plesk\Wappspector\MatchResult\MatchResultInterface;
use Plesk\Wappspector\MatchResult\Ruby as MatchResult;

class Ruby implements MatcherInterface
{
    use UpLevelMatcherTrait;

    private const RAKEFILE = 'Rakefile';

    /**
     * @throws FilesystemException
     */
    protected function doMatch(Filesystem $fs, string $path): MatchResultInterface
    {
        if (!$fs->fileExists(rtrim($path, '/') . '/' . self::RAKEFILE)) {
            return new EmptyMatchResult();
        }

        return new MatchResult($path);
    }
}
PK�M]�^�\��Matchers/Wordpress.phpnu�[���<?php

namespace Plesk\Wappspector\Matchers;

use League\Flysystem\Filesystem;
use League\Flysystem\FilesystemException;
use Plesk\Wappspector\MatchResult\EmptyMatchResult;
use Plesk\Wappspector\MatchResult\MatchResultInterface;
use Plesk\Wappspector\MatchResult\Wordpress as MatchResult;

class Wordpress implements MatcherInterface
{
    private const VERSION_FILE = 'wp-includes/version.php';

    /**
     * @throws FilesystemException
     */
    private function detectVersion(Filesystem $fs, string $path): ?string
    {
        $versionFile = rtrim($path, '/') . '/' . self::VERSION_FILE;
        preg_match("/\\\$wp_version\\s*=\\s*'([^']+)'/", $fs->read($versionFile), $matches);

        if ($matches !== []) {
            return $matches[1];
        }

        return null;
    }

    /**
     * @throws FilesystemException
     */
    private function isWordpress(Filesystem $fs, string $path): bool
    {
        $versionFile = rtrim($path, '/') . '/' . self::VERSION_FILE;

        if (!$fs->fileExists($versionFile)) {
            return false;
        }

        $fileContents = $fs->read($versionFile);

        return stripos($fileContents, '$wp_version =') !== false;
    }

    /**
     * @throws FilesystemException
     */
    public function match(Filesystem $fs, string $path): MatchResultInterface
    {
        if (!$this->isWordpress($fs, $path)) {
            return new EmptyMatchResult();
        }

        return new MatchResult($path, $this->detectVersion($fs, $path));
    }
}
PK�M]2 p��Matchers/Drupal.phpnu�[���<?php

namespace Plesk\Wappspector\Matchers;

use League\Flysystem\Filesystem;
use League\Flysystem\FilesystemException;
use Plesk\Wappspector\MatchResult\Drupal as MatchResult;
use Plesk\Wappspector\MatchResult\EmptyMatchResult;
use Plesk\Wappspector\MatchResult\MatchResultInterface;

class Drupal implements MatcherInterface
{
    /**
     * Drupal has changed the way how the version number is stored multiple times, so we need this comprehensive array
     */
    private const VERSIONS = [
        [
            'file' => 'modules/system/system.info',
            'regex' => "/version\\s*=\\s*\"(\\d\\.[^']+)\"[\\s\\S]*project\\s*=\\s*\"drupal\"/",
        ],
        [
            'file' => 'core/modules/system/system.info.yml',
            'regex' => "/version:\\s*'(\\d+\\.[^']+)'[\\s\\S]*project:\\s*'drupal'/",
        ],
    ];

    /**
     * @throws FilesystemException
     */
    public function match(Filesystem $fs, string $path): MatchResultInterface
    {
        // Iterate through version patterns
        foreach (self::VERSIONS as $version) {
            $versionFile = rtrim($path, '/') . '/' . $version['file'];

            if (!$fs->fileExists($versionFile)) {
                continue;
            }

            $version = $this->detectVersion($version['regex'], $versionFile, $fs);
            return new MatchResult($path, $version);
        }

        return new EmptyMatchResult();
    }

    private function detectVersion(string $regexPattern, string $versionFile, Filesystem $fs): ?string
    {
        preg_match($regexPattern, $fs->read($versionFile), $matches);

        return count($matches) ? $matches[1] : null;
    }
}
PK�M]?��77Matchers/DotNet.phpnu�[���<?php

namespace Plesk\Wappspector\Matchers;

use League\Flysystem\Filesystem;
use League\Flysystem\FilesystemException;
use League\Flysystem\StorageAttributes;
use Plesk\Wappspector\MatchResult\DotNet as MatchResult;
use Plesk\Wappspector\MatchResult\EmptyMatchResult;
use Plesk\Wappspector\MatchResult\MatchResultInterface;

class DotNet implements MatcherInterface
{
    use UpLevelMatcherTrait;

    private const HEX_SIGNATURE = '4d5a';

    /**
     * @throws FilesystemException
     */
    public function doMatch(Filesystem $fs, string $path): MatchResultInterface
    {
        foreach ($fs->listContents($path) as $item) {
            /** @var StorageAttributes $item */
            if (!$item->isFile() || !str_ends_with($item->path(), '.dll')) {
                continue;
            }

            $handle = $fs->readStream($item->path());
            $hex = bin2hex(fread($handle, 4));
            if (str_contains($hex, self::HEX_SIGNATURE)) {
                return new MatchResult($path);
            }
        }

        return new EmptyMatchResult();
    }
}
PK�M]%��''Matchers/Joomla.phpnu�[���<?php

namespace Plesk\Wappspector\Matchers;

use League\Flysystem\Filesystem;
use League\Flysystem\FilesystemException;
use Plesk\Wappspector\MatchResult\EmptyMatchResult;
use Plesk\Wappspector\MatchResult\Joomla as MatchResult;
use Plesk\Wappspector\MatchResult\MatchResultInterface;

class Joomla implements MatcherInterface
{
    private const CONFIG_FILE = 'configuration.php';

    /**
     * Joomla has changed the way how the version number is stored multiple times, so we need this comprehensive array
     */
    private const VERSION = [
        "files" => [
            "/includes/version.php",
            "/libraries/joomla/version.php",
            "/libraries/cms/version/version.php",
            "/libraries/src/Version.php",
        ],
        "regex_release" => "/\\\$?RELEASE\s*=\s*'([\d.]+)';/",
        "regex_devlevel" => "/\\\$?DEV_LEVEL\s*=\s*'([^']+)';/",
        "regex_major" => "/\\\$?MAJOR_VERSION\s*=\s*([\d.]+);/",
        "regex_minor" => "/\\\$?MINOR_VERSION\s*=\s*([\d.]+);/",
        "regex_patch" => "/\\\$?PATCH_VERSION\s*=\s*([\d.]+);/",
    ];

    /**
     * @throws FilesystemException
     */
    private function isJoomla(Filesystem $fs, string $path): bool
    {
        $configFile = rtrim($path, '/') . '/' . self::CONFIG_FILE;

        if (!$fs->fileExists($configFile)) {
            return false;
        }

        $configContents = $fs->read($configFile);

        if (
            stripos($configContents, 'JConfig') === false
            && stripos($configContents, 'mosConfig') === false
        ) {
            return false;
        }

        // False positive "Akeeba Backup Installer"
        if (stripos($configContents, 'class ABIConfiguration') !== false) {
            return false;
        }

        // False positive mock file in unit test folder
        if (stripos($configContents, 'Joomla.UnitTest') !== false) {
            return false;
        }

        // False positive mock file in unit test folder
        return stripos($configContents, "Joomla\Framework\Test") === false;
    }

    /**
     * @throws FilesystemException
     */
    private function detectVersion(Filesystem $fs, string $path): ?string
    {
        // Iterate through version files
        foreach (self::VERSION['files'] as $file) {
            $versionFile = rtrim($path, '/') . '/' . $file;

            if (!$fs->fileExists($versionFile)) {
                continue;
            }

            $fileContents = $fs->read($versionFile);

            preg_match(self::VERSION['regex_major'], $fileContents, $major);
            preg_match(self::VERSION['regex_minor'], $fileContents, $minor);
            preg_match(self::VERSION['regex_patch'], $fileContents, $patch);

            if (count($major) && count($minor) && count($patch)) {
                return $major[1] . '.' . $minor[1] . '.' . $patch[1];
            }

            if (count($major) && count($minor)) {
                return $major[1] . '.' . $minor[1] . 'x';
            }

            if ($major !== []) {
                return $major[1] . '.x.x';
            }

            // Legacy handling for all version < 3.8.0
            preg_match(self::VERSION['regex_release'], $fileContents, $release);
            preg_match(self::VERSION['regex_devlevel'], $fileContents, $devlevel);

            if (count($release) && count($devlevel)) {
                return $release[1] . '.' . $devlevel[1];
            }

            if ($release !== []) {
                return $release[1] . '.x';
            }
        }

        return null;
    }

    /**
     * @throws FilesystemException
     */
    public function match(Filesystem $fs, string $path): MatchResultInterface
    {
        if (!$this->isJoomla($fs, $path)) {
            return new EmptyMatchResult();
        }

        return new MatchResult($path, $this->detectVersion($fs, $path));
    }
}
PK�M]8c�'WWMatchers/MatcherInterface.phpnu�[���<?php

namespace Plesk\Wappspector\Matchers;

use League\Flysystem\Filesystem;
use Plesk\Wappspector\MatchResult\MatchResultInterface;

interface MatcherInterface
{
    /**
     * Checks filesystem by provided path and returns the list of found objects.
     */
    public function match(Filesystem $fs, string $path): MatchResultInterface;
}
PK�M]^��Z��Matchers/WebPresenceBuilder.phpnu�[���<?php

declare(strict_types=1);

namespace Plesk\Wappspector\Matchers;

use DOMDocument;
use DOMXPath;
use League\Flysystem\Filesystem;
use Plesk\Wappspector\Helper\InspectorHelper;
use Plesk\Wappspector\MatchResult\EmptyMatchResult;
use Plesk\Wappspector\MatchResult\MatchResultInterface;
use Plesk\Wappspector\MatchResult\WebPresenceBuilder as MatchResult;
use Throwable;

class WebPresenceBuilder implements MatcherInterface
{
    public function match(Filesystem $fs, string $path): MatchResultInterface
    {
        $indexHtmlPath = rtrim($path, '/') . '/index.html';
        if (!$fs->fileExists($indexHtmlPath)) {
            return new EmptyMatchResult();
        }

        $fileContent = $fs->read($indexHtmlPath);

        $inspectorHelper = new InspectorHelper();

        return $inspectorHelper->fileContentMatchesString(
            $fileContent,
            '/<meta name="generator" content="Web Presence Builder.*">/'
        ) || $this->fileContainsDOMStructure($fileContent)
            ? new MatchResult($path)
            : new EmptyMatchResult();
    }

    private function fileContainsDOMStructure(string $fileContent): bool
    {
        $dom = new DOMDocument();
        try {
            libxml_use_internal_errors(true);
            $domIsLoaded = $dom->loadHTML($fileContent);
            libxml_clear_errors();
        } catch (Throwable) {
            return false;
        }

        if ($domIsLoaded === false) {
            return false;
        }

        $xpath = new DOMXPath($dom);

        // Find the <div> with id="page"
        $pageDiv = $xpath->query("//div[@id='page']");

        if ($pageDiv->length === 0) {
            return false;
        }

        $pageNode = $pageDiv->item(0);

        // Check for direct children with the required IDs
        $watermarkDiv = $xpath->query("./div[@id='watermark']", $pageNode);
        $layoutDiv = $xpath->query("./div[@id='layout']", $pageNode);

        return $watermarkDiv->length > 0 && $layoutDiv->length > 0;
    }
}
PK�M]��n�llFileSystemFactory.phpnu�[���<?php

namespace Plesk\Wappspector;

use League\Flysystem\Filesystem;
use League\Flysystem\Local\LocalFilesystemAdapter;

class FileSystemFactory
{
    public function __invoke(string $path): Filesystem
    {
        $adapter = new LocalFilesystemAdapter($path, null, LOCK_EX, LocalFilesystemAdapter::SKIP_LINKS);

        return new Filesystem($adapter);
    }
}
PK�M]u�qq
container.phpnu�[���<?php

use DI\Container;
use Plesk\Wappspector\Command\Inspect;
use Plesk\Wappspector\FileSystemFactory;
use Plesk\Wappspector\Matchers;
use Plesk\Wappspector\Wappspector;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Application;

return [
    'matchers' => [
        Matchers\Wordpress::class,
        Matchers\Joomla::class,
        Matchers\Drupal::class,
        Matchers\Prestashop::class,
        Matchers\Typo3::class,
        Matchers\Laravel::class,
        Matchers\Symfony::class,
        Matchers\CodeIgniter::class,
        Matchers\CakePHP::class,
        Matchers\Yii::class,
        Matchers\DotNet::class,
        Matchers\Ruby::class,
        Matchers\Python::class,
        Matchers\EmDash::class,
        Matchers\NodeJs::class,
        Matchers\Sitejet::class,
        Matchers\WebPresenceBuilder::class,
        Matchers\Sitepro::class,
        Matchers\Duda::class,
        Matchers\Siteplus::class,

        // Low priority wrappers. Should go last.
        Matchers\Composer::class,
        Matchers\Php::class,
    ],
    Wappspector::class => static function (Container $container): Wappspector {
        $matchers = [];

        foreach ($container->get('matchers') as $matcher) {
            $matchers[] = $container->get($matcher);
        }

        return new Wappspector($container->get(FileSystemFactory::class), $matchers);
    },
    Inspect::class => static function (ContainerInterface $container): Inspect {
        return new Inspect($container->get(Wappspector::class));
    },
    Application::class => static function (ContainerInterface $container): Application {
        $application = new Application('Wappspector');
        $inspectCommand = $container->get(Inspect::class);

        $application->add($inspectCommand);
        $application->setDefaultCommand($inspectCommand->getName(), true);

        return $application;
    },
];
PK�M]i�Ң��Helper/InspectorHelper.phpnu�[���<?php
// Copyright 1999-2024. WebPros International GmbH. All rights reserved.
declare(strict_types=1);

namespace Plesk\Wappspector\Helper;

use League\Flysystem\Filesystem;

class InspectorHelper
{
    public function fileContentContainsString(string $fileContent, string $searchString): bool
    {
        return str_contains($fileContent, $searchString);
    }

    public function fileContentMatchesString(string $fileContent, string $searchPattern): bool
    {
        return preg_match($searchPattern, $fileContent) === 1;
    }

    public function fileContainsString(Filesystem $fs, string $filePath, string $searchString): bool
    {
        return $fs->fileExists($filePath) && str_contains($fs->read($filePath), $searchString);
    }
}
PK�M]_(+�		Wappspector.phpnu�[���<?php

namespace Plesk\Wappspector;

use Plesk\Wappspector\Matchers\MatcherInterface;
use Plesk\Wappspector\MatchResult\EmptyMatchResult;
use Plesk\Wappspector\MatchResult\MatchResultInterface;
use Throwable;

final class Wappspector
{
    /**
     * @param callable $fsFactory
     */
    public function __construct(private $fsFactory, private array $matchers)
    {
    }

    /**
     * @return MatchResultInterface[]
     * @throws Throwable
     */
    public function run(string $path, string $basePath = '/', int $matchersLimit = 0): iterable
    {
        $fs = ($this->fsFactory)($basePath);

        $result = [];

        /** @var MatcherInterface $matcher */
        foreach ($this->matchers as $matcher) {
            if (($match = $matcher->match($fs, $path)) instanceof EmptyMatchResult) {
                continue;
            }

            $result[] = $match;
            if ($matchersLimit > 0 && count($result) >= $matchersLimit) {
                break;
            }
        }

        return $result;
    }
}
PKv�]]e�ۄ�Operations/DTO/error_lognu�[���[30-May-2026 09:47:30 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php:24
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php on line 24
[11-Jun-2026 06:20:56 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php:24
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php on line 24
[20-Jun-2026 10:36:10 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php:24
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php on line 24
[21-Jun-2026 10:36:08 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php:24
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php on line 24
[10-Jul-2026 02:43:03 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php:24
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php on line 24
[17-Jul-2026 21:05:48 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php:24
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php on line 24
[18-Jul-2026 11:39:30 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php:24
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php on line 24
[24-Jul-2026 07:01:14 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php:24
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php on line 24
[02-Aug-2026 03:19:31 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php:24
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php on line 24
[02-Aug-2026 04:44:15 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php:24
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php on line 24
[09-Aug-2026 04:34:07 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php:24
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php on line 24
[13-Aug-2026 01:33:19 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php:24
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php on line 24
[13-Aug-2026 05:29:58 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php:24
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/DTO/GenerativeAiOperation.php on line 24
PKv�]���F��Operations/Enums/error_lognu�[���[30-May-2026 09:47:31 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php on line 23
[11-Jun-2026 06:20:58 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php on line 23
[20-Jun-2026 10:36:10 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php on line 23
[21-Jun-2026 10:36:09 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php on line 23
[10-Jul-2026 02:43:06 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php on line 23
[17-Jul-2026 21:05:52 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php on line 23
[18-Jul-2026 11:39:43 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php on line 23
[24-Jul-2026 07:01:15 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php on line 23
[02-Aug-2026 03:19:31 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php on line 23
[02-Aug-2026 04:44:17 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php on line 23
[09-Aug-2026 04:34:08 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php on line 23
[13-Aug-2026 01:33:17 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php on line 23
[13-Aug-2026 05:29:55 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Operations/Enums/OperationStateEnum.php on line 23
PKv�]�#��HHFiles/DTO/error_lognu�[���[30-May-2026 09:47:15 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/DTO/File.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/DTO/File.php on line 28
[11-Jun-2026 06:20:39 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/DTO/File.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/DTO/File.php on line 28
[20-Jun-2026 10:36:02 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/DTO/File.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/DTO/File.php on line 28
[21-Jun-2026 10:36:03 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/DTO/File.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/DTO/File.php on line 28
[10-Jul-2026 02:42:23 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/DTO/File.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/DTO/File.php on line 28
[17-Jul-2026 21:05:00 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/DTO/File.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/DTO/File.php on line 28
[18-Jul-2026 11:38:26 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/DTO/File.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/DTO/File.php on line 28
[24-Jul-2026 07:01:08 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/DTO/File.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/DTO/File.php on line 28
[02-Aug-2026 03:19:26 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/DTO/File.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/DTO/File.php on line 28
[02-Aug-2026 04:45:13 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/DTO/File.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/DTO/File.php on line 28
[09-Aug-2026 04:34:01 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/DTO/File.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/DTO/File.php on line 28
[13-Aug-2026 01:33:28 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/DTO/File.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/DTO/File.php on line 28
[13-Aug-2026 05:29:42 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/DTO/File.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/DTO/File.php on line 28
PKv�]�NK��%�%Files/Enums/error_lognu�[���[30-May-2026 09:47:17 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php on line 17
[30-May-2026 09:47:17 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php on line 19
[11-Jun-2026 06:20:41 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php on line 17
[11-Jun-2026 06:20:42 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php on line 19
[20-Jun-2026 10:36:03 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php on line 17
[20-Jun-2026 10:36:03 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php on line 19
[21-Jun-2026 10:36:04 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php on line 17
[21-Jun-2026 10:36:04 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php on line 19
[10-Jul-2026 02:42:27 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php on line 17
[10-Jul-2026 02:42:30 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php on line 19
[17-Jul-2026 21:05:07 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php on line 17
[17-Jul-2026 21:05:09 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php on line 19
[18-Jul-2026 11:38:31 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php on line 17
[18-Jul-2026 11:38:33 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php on line 19
[24-Jul-2026 07:01:08 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php on line 17
[24-Jul-2026 07:01:09 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php on line 19
[02-Aug-2026 03:19:27 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php on line 17
[02-Aug-2026 03:19:27 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php on line 19
[02-Aug-2026 04:45:17 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php on line 19
[02-Aug-2026 04:45:18 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php on line 17
[09-Aug-2026 04:34:02 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php on line 17
[09-Aug-2026 04:34:02 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php on line 19
[13-Aug-2026 01:33:24 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php on line 19
[13-Aug-2026 01:33:25 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php on line 17
[13-Aug-2026 05:29:44 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/FileTypeEnum.php on line 17
[13-Aug-2026 05:29:46 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Files/Enums/MediaOrientationEnum.php on line 19
PKv�]^6�F>F>Common/Exception/error_lognu�[���[30-May-2026 09:47:07 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php on line 15
[30-May-2026 09:47:08 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php on line 15
[30-May-2026 09:47:08 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php on line 15
[11-Jun-2026 06:20:29 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php on line 15
[11-Jun-2026 06:20:30 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php on line 15
[11-Jun-2026 06:20:30 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php on line 15
[20-Jun-2026 10:35:59 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php on line 15
[20-Jun-2026 10:36:00 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php on line 15
[20-Jun-2026 10:36:00 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php on line 15
[21-Jun-2026 10:36:00 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php on line 15
[21-Jun-2026 10:36:00 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php on line 15
[21-Jun-2026 10:36:00 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php on line 15
[10-Jul-2026 02:41:58 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php on line 15
[10-Jul-2026 02:42:00 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php on line 15
[10-Jul-2026 02:42:06 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php on line 15
[17-Jul-2026 21:04:34 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php on line 15
[17-Jul-2026 21:04:40 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php on line 15
[17-Jul-2026 21:04:43 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php on line 15
[18-Jul-2026 11:37:51 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php on line 15
[18-Jul-2026 11:37:53 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php on line 15
[18-Jul-2026 11:37:57 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php on line 15
[24-Jul-2026 07:01:05 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php on line 15
[24-Jul-2026 07:01:05 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php on line 15
[24-Jul-2026 07:01:05 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php on line 15
[02-Aug-2026 03:19:24 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php on line 15
[02-Aug-2026 03:19:24 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php on line 15
[02-Aug-2026 03:19:25 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php on line 15
[02-Aug-2026 04:44:27 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php on line 15
[02-Aug-2026 04:44:42 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php on line 15
[09-Aug-2026 04:33:59 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php on line 15
[09-Aug-2026 04:33:59 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php on line 15
[09-Aug-2026 04:33:59 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php on line 15
[13-Aug-2026 01:33:08 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php on line 15
[13-Aug-2026 01:33:10 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php on line 15
[13-Aug-2026 01:33:12 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php on line 15
[13-Aug-2026 05:30:09 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/RuntimeException.php on line 15
[13-Aug-2026 05:30:11 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\AiClientExceptionInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/InvalidArgumentException.php on line 15
[13-Aug-2026 05:30:13 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php:15
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/Exception/TokenLimitReachedException.php on line 15
PKv�]%��::Common/error_lognu�[���[30-May-2026 09:47:04 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php on line 28
[11-Jun-2026 06:20:24 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php on line 28
[20-Jun-2026 10:35:57 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php on line 28
[21-Jun-2026 10:35:58 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php on line 28
[10-Jul-2026 02:41:45 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php on line 28
[17-Jul-2026 21:04:13 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php on line 28
[18-Jul-2026 11:37:22 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php on line 28
[24-Jul-2026 07:01:01 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php on line 28
[02-Aug-2026 03:19:23 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php on line 28
[02-Aug-2026 04:44:22 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php on line 28
[09-Aug-2026 04:33:57 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php on line 28
[13-Aug-2026 01:32:57 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php on line 28
[13-Aug-2026 05:30:00 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithArrayTransformationInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Common/AbstractDataTransferObject.php on line 28
PKv�]7�y�K�KMessages/DTO/error_lognu�[���[30-May-2026 09:47:22 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/Message.php:26
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/Message.php on line 26
[30-May-2026 09:47:23 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php:38
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php on line 38
[30-May-2026 09:47:23 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php on line 19
[30-May-2026 09:47:23 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php on line 18
[11-Jun-2026 06:20:47 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/Message.php:26
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/Message.php on line 26
[11-Jun-2026 06:20:47 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php:38
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php on line 38
[11-Jun-2026 06:20:48 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php on line 19
[11-Jun-2026 06:20:48 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php on line 18
[20-Jun-2026 10:36:06 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/Message.php:26
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/Message.php on line 26
[20-Jun-2026 10:36:06 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php:38
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php on line 38
[20-Jun-2026 10:36:07 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php on line 19
[20-Jun-2026 10:36:07 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php on line 18
[21-Jun-2026 10:36:05 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/Message.php:26
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/Message.php on line 26
[21-Jun-2026 10:36:05 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php:38
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php on line 38
[21-Jun-2026 10:36:06 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php on line 19
[21-Jun-2026 10:36:06 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php on line 18
[10-Jul-2026 02:42:40 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/Message.php:26
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/Message.php on line 26
[10-Jul-2026 02:42:41 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php:38
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php on line 38
[10-Jul-2026 02:42:43 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php on line 19
[10-Jul-2026 02:42:43 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php on line 18
[17-Jul-2026 21:05:23 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/Message.php:26
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/Message.php on line 26
[17-Jul-2026 21:05:24 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php:38
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php on line 38
[17-Jul-2026 21:05:26 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php on line 19
[17-Jul-2026 21:05:27 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php on line 18
[18-Jul-2026 11:38:49 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/Message.php:26
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/Message.php on line 26
[18-Jul-2026 11:38:50 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php:38
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php on line 38
[18-Jul-2026 11:38:52 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php on line 19
[18-Jul-2026 11:38:54 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php on line 18
[24-Jul-2026 07:01:10 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/Message.php:26
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/Message.php on line 26
[24-Jul-2026 07:01:10 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php:38
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php on line 38
[24-Jul-2026 07:01:10 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php on line 19
[24-Jul-2026 07:01:11 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php on line 18
[02-Aug-2026 03:19:28 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/Message.php:26
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/Message.php on line 26
[02-Aug-2026 03:19:28 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php:38
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php on line 38
[02-Aug-2026 03:19:28 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php on line 19
[02-Aug-2026 03:19:29 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php on line 18
[02-Aug-2026 04:45:43 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php:38
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php on line 38
[02-Aug-2026 04:45:44 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php on line 19
[02-Aug-2026 04:45:45 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/Message.php:26
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/Message.php on line 26
[02-Aug-2026 04:45:46 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php on line 18
[09-Aug-2026 04:34:03 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/Message.php:26
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/Message.php on line 26
[09-Aug-2026 04:34:04 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php:38
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php on line 38
[09-Aug-2026 04:34:04 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php on line 19
[09-Aug-2026 04:34:04 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php on line 18
[13-Aug-2026 01:35:22 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php on line 19
[13-Aug-2026 01:35:24 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php on line 18
[13-Aug-2026 01:35:25 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/Message.php:26
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/Message.php on line 26
[13-Aug-2026 01:35:26 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php:38
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php on line 38
[13-Aug-2026 05:31:53 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/ModelMessage.php on line 19
[13-Aug-2026 05:31:54 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/Message.php:26
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/Message.php on line 26
[13-Aug-2026 05:31:55 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Messages\DTO\Message" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/UserMessage.php on line 18
[13-Aug-2026 05:31:56 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php:38
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/DTO/MessagePart.php on line 38
PKv�]�MEјM�MMessages/Enums/error_lognu�[���[30-May-2026 09:47:25 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php on line 17
[30-May-2026 09:47:25 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php:21
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php on line 21
[30-May-2026 09:47:25 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php on line 17
[30-May-2026 09:47:26 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php on line 23
[11-Jun-2026 06:20:50 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php on line 17
[11-Jun-2026 06:20:50 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php:21
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php on line 21
[11-Jun-2026 06:20:50 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php on line 17
[11-Jun-2026 06:20:51 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php on line 23
[20-Jun-2026 10:36:07 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php on line 17
[20-Jun-2026 10:36:08 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php:21
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php on line 21
[20-Jun-2026 10:36:08 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php on line 17
[20-Jun-2026 10:36:08 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php on line 23
[21-Jun-2026 10:36:06 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php on line 17
[21-Jun-2026 10:36:07 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php:21
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php on line 21
[21-Jun-2026 10:36:07 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php on line 17
[21-Jun-2026 10:36:07 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php on line 23
[10-Jul-2026 02:42:47 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php on line 17
[10-Jul-2026 02:42:49 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php:21
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php on line 21
[10-Jul-2026 02:42:52 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php on line 17
[10-Jul-2026 02:42:54 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php on line 23
[17-Jul-2026 21:05:31 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php on line 17
[17-Jul-2026 21:05:33 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php:21
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php on line 21
[17-Jul-2026 21:05:34 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php on line 17
[17-Jul-2026 21:05:37 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php on line 23
[18-Jul-2026 11:39:01 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php on line 17
[18-Jul-2026 11:39:03 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php:21
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php on line 21
[18-Jul-2026 11:39:06 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php on line 17
[18-Jul-2026 11:39:08 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php on line 23
[24-Jul-2026 07:01:11 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php on line 17
[24-Jul-2026 07:01:11 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php:21
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php on line 21
[24-Jul-2026 07:01:12 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php on line 17
[24-Jul-2026 07:01:12 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php on line 23
[02-Aug-2026 03:19:29 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php on line 17
[02-Aug-2026 03:19:29 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php:21
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php on line 21
[02-Aug-2026 03:19:29 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php on line 17
[02-Aug-2026 03:19:30 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php on line 23
[02-Aug-2026 04:45:49 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php on line 23
[02-Aug-2026 04:45:53 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php:21
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php on line 21
[02-Aug-2026 04:45:55 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php on line 17
[02-Aug-2026 04:45:58 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php on line 17
[09-Aug-2026 04:34:05 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php on line 17
[09-Aug-2026 04:34:05 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php:21
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php on line 21
[09-Aug-2026 04:34:05 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php on line 17
[09-Aug-2026 04:34:06 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php on line 23
[13-Aug-2026 01:35:15 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php on line 17
[13-Aug-2026 01:35:17 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php on line 17
[13-Aug-2026 01:35:18 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php on line 23
[13-Aug-2026 01:35:20 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php:21
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php on line 21
[13-Aug-2026 05:31:59 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessageRoleEnum.php on line 17
[13-Aug-2026 05:32:00 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartChannelEnum.php on line 17
[13-Aug-2026 05:32:02 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/ModalityEnum.php on line 23
[13-Aug-2026 05:32:03 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php:21
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Messages/Enums/MessagePartTypeEnum.php on line 21
PKw�]}��zdzdProviders/Models/DTO/error_lognu�[���[30-May-2026 09:48:04 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php:51
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php on line 51
[30-May-2026 09:48:05 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php on line 28
[30-May-2026 09:48:05 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php on line 29
[30-May-2026 09:48:05 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php on line 23
[30-May-2026 09:48:05 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php:25
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php on line 25
[11-Jun-2026 06:21:43 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php:51
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php on line 51
[11-Jun-2026 06:21:43 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php on line 28
[11-Jun-2026 06:21:43 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php on line 29
[11-Jun-2026 06:21:44 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php on line 23
[11-Jun-2026 06:21:44 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php:25
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php on line 25
[20-Jun-2026 10:36:28 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php:51
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php on line 51
[20-Jun-2026 10:36:29 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php on line 28
[20-Jun-2026 10:36:29 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php on line 29
[20-Jun-2026 10:36:30 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php on line 23
[20-Jun-2026 10:36:31 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php:25
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php on line 25
[21-Jun-2026 10:36:23 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php:51
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php on line 51
[21-Jun-2026 10:36:23 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php on line 28
[21-Jun-2026 10:36:23 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php on line 29
[21-Jun-2026 10:36:24 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php on line 23
[21-Jun-2026 10:36:24 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php:25
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php on line 25
[10-Jul-2026 02:44:44 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php:51
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php on line 51
[10-Jul-2026 02:44:44 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php on line 28
[10-Jul-2026 02:44:46 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php on line 29
[10-Jul-2026 02:44:47 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php on line 23
[10-Jul-2026 02:44:47 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php:25
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php on line 25
[17-Jul-2026 21:08:01 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php:51
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php on line 51
[17-Jul-2026 21:08:04 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php on line 28
[17-Jul-2026 21:08:04 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php on line 29
[17-Jul-2026 21:08:06 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php on line 23
[17-Jul-2026 21:08:08 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php:25
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php on line 25
[18-Jul-2026 11:43:26 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php:51
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php on line 51
[18-Jul-2026 11:43:30 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php on line 28
[18-Jul-2026 11:43:32 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php on line 29
[18-Jul-2026 11:43:34 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php on line 23
[18-Jul-2026 11:43:35 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php:25
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php on line 25
[24-Jul-2026 07:01:30 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php:51
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php on line 51
[24-Jul-2026 07:01:30 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php on line 28
[24-Jul-2026 07:01:30 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php on line 29
[24-Jul-2026 07:01:31 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php on line 23
[24-Jul-2026 07:01:31 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php:25
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php on line 25
[02-Aug-2026 03:19:44 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php:51
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php on line 51
[02-Aug-2026 03:19:44 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php on line 28
[02-Aug-2026 03:19:44 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php on line 29
[02-Aug-2026 03:19:44 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php on line 23
[02-Aug-2026 03:19:44 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php:25
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php on line 25
[02-Aug-2026 04:46:54 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php:25
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php on line 25
[02-Aug-2026 04:46:55 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php:51
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php on line 51
[02-Aug-2026 04:46:58 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php on line 23
[02-Aug-2026 04:47:00 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php on line 28
[09-Aug-2026 04:34:23 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php:51
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php on line 51
[09-Aug-2026 04:34:23 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php on line 28
[09-Aug-2026 04:34:24 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php on line 29
[09-Aug-2026 04:34:24 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php on line 23
[09-Aug-2026 04:34:24 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php:25
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php on line 25
[13-Aug-2026 01:34:47 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php on line 28
[13-Aug-2026 01:34:48 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php on line 29
[13-Aug-2026 01:34:49 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php on line 23
[13-Aug-2026 01:34:51 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php:25
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php on line 25
[13-Aug-2026 01:34:52 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php:51
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php on line 51
[13-Aug-2026 05:31:29 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelRequirements.php on line 29
[13-Aug-2026 05:31:30 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php:28
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelMetadata.php on line 28
[13-Aug-2026 05:31:32 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/RequiredOption.php on line 23
[13-Aug-2026 05:31:33 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php:25
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/SupportedOption.php on line 25
[13-Aug-2026 05:31:34 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php:51
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/DTO/ModelConfig.php on line 51
PKw�]��h'h' Providers/Models/Enums/error_lognu�[���[30-May-2026 09:48:07 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php on line 29
[30-May-2026 09:48:07 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php:65
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php on line 65
[11-Jun-2026 06:21:46 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php on line 29
[11-Jun-2026 06:21:46 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php:65
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php on line 65
[20-Jun-2026 10:36:32 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php on line 29
[20-Jun-2026 10:36:32 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php:65
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php on line 65
[21-Jun-2026 10:36:24 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php on line 29
[21-Jun-2026 10:36:25 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php:65
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php on line 65
[10-Jul-2026 02:44:51 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php on line 29
[10-Jul-2026 02:44:52 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php:65
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php on line 65
[17-Jul-2026 21:08:13 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php on line 29
[17-Jul-2026 21:08:17 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php:65
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php on line 65
[18-Jul-2026 11:43:40 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php on line 29
[18-Jul-2026 11:43:41 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php:65
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php on line 65
[24-Jul-2026 07:01:31 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php on line 29
[24-Jul-2026 07:01:32 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php:65
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php on line 65
[02-Aug-2026 03:19:45 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php on line 29
[02-Aug-2026 03:19:45 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php:65
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php on line 65
[02-Aug-2026 04:47:16 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php:65
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php on line 65
[02-Aug-2026 04:47:17 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php on line 29
[09-Aug-2026 04:34:25 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php on line 29
[09-Aug-2026 04:34:25 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php:65
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php on line 65
[13-Aug-2026 01:34:40 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php:65
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php on line 65
[13-Aug-2026 01:34:41 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php on line 29
[13-Aug-2026 05:31:25 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/CapabilityEnum.php on line 29
[13-Aug-2026 05:31:26 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php:65
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Models/Enums/OptionEnum.php on line 65
PKw�]?&'�d�d*Providers/ApiBasedImplementation/error_lognu�[���[30-May-2026 09:47:34 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModel.php on line 23
[30-May-2026 09:47:35 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php on line 21
[30-May-2026 09:47:35 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\AbstractProvider" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php on line 16
[30-May-2026 09:47:36 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php on line 23
[30-May-2026 09:47:37 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php on line 18
[11-Jun-2026 06:21:02 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModel.php on line 23
[11-Jun-2026 06:21:03 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php on line 21
[11-Jun-2026 06:21:03 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\AbstractProvider" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php on line 16
[11-Jun-2026 06:21:06 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php on line 23
[11-Jun-2026 06:21:06 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php on line 18
[20-Jun-2026 10:36:12 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModel.php on line 23
[20-Jun-2026 10:36:12 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php on line 21
[20-Jun-2026 10:36:12 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\AbstractProvider" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php on line 16
[20-Jun-2026 10:36:13 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php on line 23
[20-Jun-2026 10:36:13 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php on line 18
[21-Jun-2026 10:36:10 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModel.php on line 23
[21-Jun-2026 10:36:10 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php on line 21
[21-Jun-2026 10:36:10 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\AbstractProvider" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php on line 16
[21-Jun-2026 10:36:11 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php on line 23
[21-Jun-2026 10:36:11 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php on line 18
[10-Jul-2026 02:43:14 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModel.php on line 23
[10-Jul-2026 02:43:17 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php on line 21
[10-Jul-2026 02:43:19 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\AbstractProvider" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php on line 16
[10-Jul-2026 02:43:25 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php on line 23
[10-Jul-2026 02:43:27 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php on line 18
[17-Jul-2026 21:06:06 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModel.php on line 23
[17-Jul-2026 21:06:07 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php on line 21
[17-Jul-2026 21:06:09 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\AbstractProvider" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php on line 16
[17-Jul-2026 21:06:15 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php on line 23
[17-Jul-2026 21:06:17 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php on line 18
[18-Jul-2026 11:39:54 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModel.php on line 23
[18-Jul-2026 11:39:56 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php on line 21
[18-Jul-2026 11:40:00 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\AbstractProvider" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php on line 16
[18-Jul-2026 11:40:19 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php on line 23
[18-Jul-2026 11:40:23 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php on line 18
[24-Jul-2026 07:01:17 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModel.php on line 23
[24-Jul-2026 07:01:18 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php on line 21
[24-Jul-2026 07:01:18 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\AbstractProvider" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php on line 16
[24-Jul-2026 07:01:19 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php on line 23
[24-Jul-2026 07:01:19 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php on line 18
[02-Aug-2026 03:19:32 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModel.php on line 23
[02-Aug-2026 03:19:32 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php on line 21
[02-Aug-2026 03:19:32 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\AbstractProvider" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php on line 16
[02-Aug-2026 03:19:33 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php on line 23
[02-Aug-2026 03:19:33 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php on line 18
[02-Aug-2026 04:46:06 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php on line 18
[02-Aug-2026 04:46:08 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php on line 21
[02-Aug-2026 04:46:11 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\AbstractProvider" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php on line 16
[02-Aug-2026 04:46:19 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModel.php on line 23
[02-Aug-2026 04:46:26 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php on line 23
[09-Aug-2026 04:34:09 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModel.php on line 23
[09-Aug-2026 04:34:09 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php on line 21
[09-Aug-2026 04:34:10 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\AbstractProvider" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php on line 16
[09-Aug-2026 04:34:10 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php on line 23
[09-Aug-2026 04:34:11 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php on line 18
[13-Aug-2026 01:33:43 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModel.php on line 23
[13-Aug-2026 01:33:44 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\AbstractProvider" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php on line 16
[13-Aug-2026 01:33:45 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php on line 23
[13-Aug-2026 01:33:47 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php on line 18
[13-Aug-2026 01:33:49 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php on line 21
[13-Aug-2026 05:30:24 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModel.php on line 23
[13-Aug-2026 05:30:25 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiBasedModelMetadataDirectory.php on line 21
[13-Aug-2026 05:30:26 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/GenerateTextApiBasedProviderAvailability.php on line 23
[13-Aug-2026 05:30:28 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\AbstractProvider" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/AbstractApiProvider.php on line 16
[13-Aug-2026 05:30:29 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/ListModelsApiBasedProviderAvailability.php on line 18
PKw�]?@��4Providers/ApiBasedImplementation/Contracts/error_lognu�[���[30-May-2026 09:47:36 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Models\Contracts\ModelInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php on line 16
[11-Jun-2026 06:21:06 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Models\Contracts\ModelInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php on line 16
[20-Jun-2026 10:36:13 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Models\Contracts\ModelInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php on line 16
[21-Jun-2026 10:36:11 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Models\Contracts\ModelInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php on line 16
[10-Jul-2026 02:43:24 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Models\Contracts\ModelInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php on line 16
[17-Jul-2026 21:06:13 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Models\Contracts\ModelInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php on line 16
[18-Jul-2026 11:40:15 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Models\Contracts\ModelInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php on line 16
[24-Jul-2026 07:01:18 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Models\Contracts\ModelInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php on line 16
[02-Aug-2026 03:19:33 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Models\Contracts\ModelInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php on line 16
[02-Aug-2026 04:46:31 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Models\Contracts\ModelInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php on line 16
[09-Aug-2026 04:34:10 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Models\Contracts\ModelInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php on line 16
[13-Aug-2026 01:33:52 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Models\Contracts\ModelInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php on line 16
[13-Aug-2026 05:30:31 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Models\Contracts\ModelInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ApiBasedImplementation/Contracts/ApiBasedModelInterface.php on line 16
PKw�]HF��l(l(Providers/DTO/error_lognu�[���[30-May-2026 09:47:40 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php:32
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php on line 32
[30-May-2026 09:47:41 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php:27
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php on line 27
[11-Jun-2026 06:21:11 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php:32
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php on line 32
[11-Jun-2026 06:21:12 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php:27
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php on line 27
[20-Jun-2026 10:36:16 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php:32
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php on line 32
[20-Jun-2026 10:36:16 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php:27
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php on line 27
[21-Jun-2026 10:36:13 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php:32
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php on line 32
[21-Jun-2026 10:36:13 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php:27
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php on line 27
[10-Jul-2026 02:43:35 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php:32
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php on line 32
[10-Jul-2026 02:43:39 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php:27
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php on line 27
[17-Jul-2026 21:06:34 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php:32
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php on line 32
[17-Jul-2026 21:06:36 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php:27
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php on line 27
[18-Jul-2026 11:40:50 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php:32
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php on line 32
[18-Jul-2026 11:40:52 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php:27
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php on line 27
[24-Jul-2026 07:01:21 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php:32
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php on line 32
[24-Jul-2026 07:01:21 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php:27
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php on line 27
[02-Aug-2026 03:19:35 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php:32
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php on line 32
[02-Aug-2026 03:19:35 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php:27
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php on line 27
[02-Aug-2026 04:46:46 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php:27
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php on line 27
[02-Aug-2026 04:46:48 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php:32
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php on line 32
[09-Aug-2026 04:34:12 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php:32
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php on line 32
[09-Aug-2026 04:34:13 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php:27
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php on line 27
[13-Aug-2026 01:35:00 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php:32
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php on line 32
[13-Aug-2026 01:35:02 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php:27
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php on line 27
[13-Aug-2026 05:30:38 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php:32
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderMetadata.php on line 32
[13-Aug-2026 05:30:40 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php:27
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/DTO/ProviderModelsMetadata.php on line 27
PKw�]�f��"Providers/Http/Abstracts/error_lognu�[���[30-May-2026 09:47:45 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php on line 20
[11-Jun-2026 06:21:18 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php on line 20
[20-Jun-2026 10:36:18 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php on line 20
[21-Jun-2026 10:36:15 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php on line 20
[10-Jul-2026 02:43:50 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php on line 20
[17-Jul-2026 21:06:53 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php on line 20
[18-Jul-2026 11:41:26 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php on line 20
[24-Jul-2026 07:01:22 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php on line 20
[02-Aug-2026 03:19:36 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php on line 20
[02-Aug-2026 04:47:50 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php on line 20
[09-Aug-2026 04:34:14 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php on line 20
[13-Aug-2026 01:34:24 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php on line 20
[13-Aug-2026 05:30:52 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClientDependencies\Http\Discovery\Strategy\DiscoveryStrategy" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Abstracts/AbstractClientDiscoveryStrategy.php on line 20
PKw�]���i�i"Providers/Http/Exception/error_lognu�[���[30-May-2026 09:47:54 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\InvalidArgumentException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php on line 18
[30-May-2026 09:47:54 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php on line 17
[30-May-2026 09:47:54 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php on line 17
[30-May-2026 09:47:55 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php on line 16
[30-May-2026 09:47:55 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php on line 17
[11-Jun-2026 06:21:31 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\InvalidArgumentException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php on line 18
[11-Jun-2026 06:21:31 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php on line 17
[11-Jun-2026 06:21:31 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php on line 17
[11-Jun-2026 06:21:31 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php on line 16
[11-Jun-2026 06:21:32 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php on line 17
[20-Jun-2026 10:36:24 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\InvalidArgumentException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php on line 18
[20-Jun-2026 10:36:25 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php on line 17
[20-Jun-2026 10:36:25 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php on line 17
[20-Jun-2026 10:36:25 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php on line 16
[20-Jun-2026 10:36:25 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php on line 17
[21-Jun-2026 10:36:19 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\InvalidArgumentException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php on line 18
[21-Jun-2026 10:36:19 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php on line 17
[21-Jun-2026 10:36:19 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php on line 17
[21-Jun-2026 10:36:20 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php on line 16
[21-Jun-2026 10:36:20 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php on line 17
[10-Jul-2026 02:44:23 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\InvalidArgumentException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php on line 18
[10-Jul-2026 02:44:25 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php on line 17
[10-Jul-2026 02:44:26 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php on line 17
[10-Jul-2026 02:44:27 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php on line 16
[10-Jul-2026 02:44:28 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php on line 17
[17-Jul-2026 21:07:29 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\InvalidArgumentException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php on line 18
[17-Jul-2026 21:07:30 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php on line 17
[17-Jul-2026 21:07:34 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php on line 17
[17-Jul-2026 21:07:35 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php on line 16
[17-Jul-2026 21:07:37 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php on line 17
[18-Jul-2026 11:42:36 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\InvalidArgumentException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php on line 18
[18-Jul-2026 11:42:38 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php on line 17
[18-Jul-2026 11:42:40 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php on line 17
[18-Jul-2026 11:42:41 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php on line 16
[18-Jul-2026 11:42:43 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php on line 17
[24-Jul-2026 07:01:27 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\InvalidArgumentException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php on line 18
[24-Jul-2026 07:01:27 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php on line 17
[24-Jul-2026 07:01:27 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php on line 17
[24-Jul-2026 07:01:27 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php on line 16
[24-Jul-2026 07:01:27 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php on line 17
[02-Aug-2026 03:19:40 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\InvalidArgumentException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php on line 18
[02-Aug-2026 03:19:40 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php on line 17
[02-Aug-2026 03:19:40 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php on line 17
[02-Aug-2026 03:19:41 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php on line 16
[02-Aug-2026 03:19:41 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php on line 17
[02-Aug-2026 04:48:01 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php on line 17
[02-Aug-2026 04:48:05 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php on line 17
[02-Aug-2026 04:48:06 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\InvalidArgumentException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php on line 18
[02-Aug-2026 04:48:09 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php on line 17
[02-Aug-2026 04:48:15 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php on line 16
[09-Aug-2026 04:34:19 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\InvalidArgumentException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php on line 18
[09-Aug-2026 04:34:19 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php on line 17
[09-Aug-2026 04:34:19 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php on line 17
[09-Aug-2026 04:34:20 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php on line 16
[09-Aug-2026 04:34:20 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php on line 17
[13-Aug-2026 01:34:13 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php on line 17
[13-Aug-2026 01:34:14 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php on line 17
[13-Aug-2026 01:34:16 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\InvalidArgumentException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php on line 18
[13-Aug-2026 01:34:18 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php on line 17
[13-Aug-2026 01:34:19 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php on line 16
[13-Aug-2026 05:31:08 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/RedirectException.php on line 17
[13-Aug-2026 05:31:09 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/NetworkException.php on line 17
[13-Aug-2026 05:31:10 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\InvalidArgumentException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ClientException.php on line 18
[13-Aug-2026 05:31:11 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php:16
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ResponseException.php on line 16
[13-Aug-2026 05:31:13 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\Exception\RuntimeException" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Exception/ServerException.php on line 17
PKw�]-��P�PProviders/Http/DTO/error_lognu�[���[30-May-2026 09:47:50 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php on line 19
[30-May-2026 09:47:50 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php:31
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php on line 31
[30-May-2026 09:47:50 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php on line 23
[30-May-2026 09:47:51 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php:25
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php on line 25
[11-Jun-2026 06:21:25 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php on line 19
[11-Jun-2026 06:21:26 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php:31
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php on line 31
[11-Jun-2026 06:21:26 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php on line 23
[11-Jun-2026 06:21:26 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php:25
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php on line 25
[20-Jun-2026 10:36:22 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php on line 19
[20-Jun-2026 10:36:22 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php:31
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php on line 31
[20-Jun-2026 10:36:22 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php on line 23
[20-Jun-2026 10:36:23 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php:25
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php on line 25
[21-Jun-2026 10:36:17 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php on line 19
[21-Jun-2026 10:36:17 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php:31
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php on line 31
[21-Jun-2026 10:36:18 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php on line 23
[21-Jun-2026 10:36:18 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php:25
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php on line 25
[10-Jul-2026 02:44:06 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php on line 19
[10-Jul-2026 02:44:08 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php:31
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php on line 31
[10-Jul-2026 02:44:12 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php on line 23
[10-Jul-2026 02:44:15 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php:25
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php on line 25
[17-Jul-2026 21:07:11 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php on line 19
[17-Jul-2026 21:07:12 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php:31
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php on line 31
[17-Jul-2026 21:07:13 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php on line 23
[17-Jul-2026 21:07:14 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php:25
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php on line 25
[18-Jul-2026 11:41:57 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php on line 19
[18-Jul-2026 11:42:07 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php:31
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php on line 31
[18-Jul-2026 11:42:17 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php on line 23
[18-Jul-2026 11:42:20 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php:25
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php on line 25
[24-Jul-2026 07:01:24 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php on line 19
[24-Jul-2026 07:01:25 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php:31
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php on line 31
[24-Jul-2026 07:01:25 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php on line 23
[24-Jul-2026 07:01:25 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php:25
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php on line 25
[02-Aug-2026 03:19:38 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php on line 19
[02-Aug-2026 03:19:39 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php:31
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php on line 31
[02-Aug-2026 03:19:39 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php on line 23
[02-Aug-2026 03:19:39 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php:25
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php on line 25
[02-Aug-2026 04:48:17 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php:31
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php on line 31
[02-Aug-2026 04:48:19 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php on line 19
[02-Aug-2026 04:48:21 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php on line 23
[02-Aug-2026 04:48:22 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php:25
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php on line 25
[09-Aug-2026 04:34:17 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php on line 19
[09-Aug-2026 04:34:17 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php:31
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php on line 31
[09-Aug-2026 04:34:17 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php on line 23
[09-Aug-2026 04:34:17 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php:25
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php on line 25
[13-Aug-2026 01:34:31 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php on line 19
[13-Aug-2026 01:34:32 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php on line 23
[13-Aug-2026 01:34:34 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php:31
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php on line 31
[13-Aug-2026 01:34:35 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php:25
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php on line 25
[13-Aug-2026 05:30:59 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php:31
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Request.php on line 31
[13-Aug-2026 05:31:01 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/ApiKeyRequestAuthentication.php on line 19
[13-Aug-2026 05:31:02 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php:25
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/Response.php on line 25
[13-Aug-2026 05:31:04 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/DTO/RequestOptions.php on line 23
PKw�]�8a�(�(Providers/Http/Enums/error_lognu�[���[30-May-2026 09:47:52 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php:32
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php on line 32
[30-May-2026 09:47:52 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php on line 18
[11-Jun-2026 06:21:28 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php:32
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php on line 32
[11-Jun-2026 06:21:29 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php on line 18
[20-Jun-2026 10:36:23 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php:32
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php on line 32
[20-Jun-2026 10:36:23 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php on line 18
[21-Jun-2026 10:36:18 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php:32
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php on line 32
[21-Jun-2026 10:36:18 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php on line 18
[10-Jul-2026 02:44:19 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php:32
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php on line 32
[10-Jul-2026 02:44:20 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php on line 18
[17-Jul-2026 21:07:16 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php:32
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php on line 32
[17-Jul-2026 21:07:22 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php on line 18
[18-Jul-2026 11:42:26 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php:32
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php on line 32
[18-Jul-2026 11:42:29 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php on line 18
[24-Jul-2026 07:01:26 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php:32
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php on line 32
[24-Jul-2026 07:01:26 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php on line 18
[02-Aug-2026 03:19:40 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php:32
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php on line 32
[02-Aug-2026 03:19:40 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php on line 18
[02-Aug-2026 04:48:32 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php on line 18
[02-Aug-2026 04:48:34 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php:32
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php on line 32
[09-Aug-2026 04:34:18 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php:32
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php on line 32
[09-Aug-2026 04:34:18 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php on line 18
[13-Aug-2026 01:34:09 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php:32
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php on line 32
[13-Aug-2026 01:34:11 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php on line 18
[13-Aug-2026 05:30:56 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php:32
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/HttpMethodEnum.php on line 32
[13-Aug-2026 05:30:57 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Enums/RequestAuthenticationMethod.php on line 18
PKw�]j
����Providers/Http/error_lognu�[���[30-May-2026 09:47:55 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php on line 29
[11-Jun-2026 06:21:32 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php on line 29
[20-Jun-2026 10:36:26 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php on line 29
[21-Jun-2026 10:36:20 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php on line 29
[10-Jul-2026 02:44:29 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php on line 29
[17-Jul-2026 21:07:38 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php on line 29
[18-Jul-2026 11:42:46 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php on line 29
[24-Jul-2026 07:01:28 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php on line 29
[02-Aug-2026 03:19:41 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php on line 29
[02-Aug-2026 04:47:42 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php on line 29
[09-Aug-2026 04:34:20 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php on line 29
[13-Aug-2026 01:34:00 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php on line 29
[13-Aug-2026 05:30:48 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Http\Contracts\HttpTransporterInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php:29
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/HttpTransporter.php on line 29
PKw�]QYD�"Providers/Http/Contracts/error_lognu�[���[30-May-2026 09:47:48 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php:13
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php on line 13
[11-Jun-2026 06:21:23 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php:13
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php on line 13
[20-Jun-2026 10:36:21 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php:13
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php on line 13
[21-Jun-2026 10:36:16 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php:13
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php on line 13
[10-Jul-2026 02:43:57 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php:13
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php on line 13
[17-Jul-2026 21:07:06 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php:13
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php on line 13
[18-Jul-2026 11:41:46 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php:13
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php on line 13
[24-Jul-2026 07:01:24 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php:13
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php on line 13
[02-Aug-2026 03:19:38 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php:13
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php on line 13
[02-Aug-2026 04:48:26 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php:13
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php on line 13
[09-Aug-2026 04:34:16 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php:13
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php on line 13
[13-Aug-2026 01:34:04 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php:13
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php on line 13
[13-Aug-2026 05:31:17 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Common\Contracts\WithJsonSchemaInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php:13
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Http/Contracts/RequestAuthenticationInterface.php on line 13
PKx�]QM�d&d&Providers/Enums/error_lognu�[���[30-May-2026 09:47:42 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php on line 19
[30-May-2026 09:47:42 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php on line 17
[11-Jun-2026 06:21:14 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php on line 19
[11-Jun-2026 06:21:14 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php on line 17
[20-Jun-2026 10:36:17 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php on line 19
[20-Jun-2026 10:36:17 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php on line 17
[21-Jun-2026 10:36:13 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php on line 19
[21-Jun-2026 10:36:14 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php on line 17
[10-Jul-2026 02:43:43 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php on line 19
[10-Jul-2026 02:43:44 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php on line 17
[17-Jul-2026 21:06:42 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php on line 19
[17-Jul-2026 21:06:47 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php on line 17
[18-Jul-2026 11:41:09 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php on line 19
[18-Jul-2026 11:41:14 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php on line 17
[24-Jul-2026 07:01:21 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php on line 19
[24-Jul-2026 07:01:22 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php on line 17
[02-Aug-2026 03:19:36 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php on line 19
[02-Aug-2026 03:19:36 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php on line 17
[02-Aug-2026 04:47:36 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php on line 19
[02-Aug-2026 04:47:39 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php on line 17
[09-Aug-2026 04:34:13 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php on line 19
[09-Aug-2026 04:34:13 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php on line 17
[13-Aug-2026 01:33:55 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php on line 17
[13-Aug-2026 01:33:56 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php on line 19
[13-Aug-2026 05:30:34 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ProviderTypeEnum.php on line 19
[13-Aug-2026 05:30:35 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php:17
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/Enums/ToolTypeEnum.php on line 17
PKx�]{e�? ? Providers/error_lognu�[���[30-May-2026 09:47:33 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/AbstractProvider.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/AbstractProvider.php on line 18
[30-May-2026 09:48:26 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ProviderRegistry.php on line 31
[11-Jun-2026 06:21:00 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/AbstractProvider.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/AbstractProvider.php on line 18
[11-Jun-2026 06:22:10 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ProviderRegistry.php on line 31
[20-Jun-2026 10:36:11 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/AbstractProvider.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/AbstractProvider.php on line 18
[20-Jun-2026 10:36:40 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ProviderRegistry.php on line 31
[21-Jun-2026 10:36:09 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/AbstractProvider.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/AbstractProvider.php on line 18
[21-Jun-2026 10:36:31 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ProviderRegistry.php on line 31
[10-Jul-2026 02:43:11 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/AbstractProvider.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/AbstractProvider.php on line 18
[10-Jul-2026 02:45:27 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ProviderRegistry.php on line 31
[17-Jul-2026 21:06:00 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/AbstractProvider.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/AbstractProvider.php on line 18
[17-Jul-2026 21:09:13 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ProviderRegistry.php on line 31
[18-Jul-2026 11:39:48 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/AbstractProvider.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/AbstractProvider.php on line 18
[18-Jul-2026 11:45:02 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ProviderRegistry.php on line 31
[24-Jul-2026 07:01:16 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/AbstractProvider.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/AbstractProvider.php on line 18
[24-Jul-2026 07:01:37 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ProviderRegistry.php on line 31
[02-Aug-2026 03:19:32 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/AbstractProvider.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/AbstractProvider.php on line 18
[02-Aug-2026 03:19:50 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ProviderRegistry.php on line 31
[02-Aug-2026 04:46:02 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ProviderRegistry.php on line 31
[02-Aug-2026 04:46:05 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/AbstractProvider.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/AbstractProvider.php on line 18
[09-Aug-2026 04:34:08 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/AbstractProvider.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/AbstractProvider.php on line 18
[09-Aug-2026 04:34:31 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ProviderRegistry.php on line 31
[13-Aug-2026 01:33:33 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/AbstractProvider.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/AbstractProvider.php on line 18
[13-Aug-2026 01:33:35 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ProviderRegistry.php on line 31
[13-Aug-2026 05:30:16 UTC] PHP Fatal error:  Trait "WordPress\AiClient\Providers\Http\Traits\WithHttpTransporterTrait" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/ProviderRegistry.php on line 31
[13-Aug-2026 05:30:18 UTC] PHP Fatal error:  Uncaught Error: Interface "WordPress\AiClient\Providers\Contracts\ProviderInterface" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/AbstractProvider.php:18
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/AbstractProvider.php on line 18
PKx�]mJ��P�P2Providers/OpenAiCompatibleImplementation/error_lognu�[���[30-May-2026 09:48:25 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php:57
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php on line 57
[30-May-2026 09:48:25 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php:22
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php on line 22
[30-May-2026 09:48:25 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php:64
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php on line 64
[11-Jun-2026 06:22:09 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php:57
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php on line 57
[11-Jun-2026 06:22:09 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php:22
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php on line 22
[11-Jun-2026 06:22:10 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php:64
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php on line 64
[20-Jun-2026 10:36:39 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php:57
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php on line 57
[20-Jun-2026 10:36:39 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php:22
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php on line 22
[20-Jun-2026 10:36:39 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php:64
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php on line 64
[21-Jun-2026 10:36:30 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php:57
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php on line 57
[21-Jun-2026 10:36:31 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php:22
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php on line 22
[21-Jun-2026 10:36:31 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php:64
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php on line 64
[10-Jul-2026 02:45:25 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php:57
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php on line 57
[10-Jul-2026 02:45:26 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php:22
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php on line 22
[10-Jul-2026 02:45:26 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php:64
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php on line 64
[17-Jul-2026 21:09:02 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php:57
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php on line 57
[17-Jul-2026 21:09:09 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php:22
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php on line 22
[17-Jul-2026 21:09:12 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php:64
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php on line 64
[18-Jul-2026 11:44:52 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php:57
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php on line 57
[18-Jul-2026 11:44:57 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php:22
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php on line 22
[18-Jul-2026 11:44:57 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php:64
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php on line 64
[24-Jul-2026 07:01:36 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php:57
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php on line 57
[24-Jul-2026 07:01:36 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php:22
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php on line 22
[24-Jul-2026 07:01:36 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php:64
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php on line 64
[02-Aug-2026 03:19:49 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php:57
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php on line 57
[02-Aug-2026 03:19:49 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php:22
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php on line 22
[02-Aug-2026 03:19:50 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php:64
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php on line 64
[02-Aug-2026 04:46:36 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php:64
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php on line 64
[02-Aug-2026 04:46:38 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php:57
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php on line 57
[02-Aug-2026 04:46:40 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php:22
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php on line 22
[09-Aug-2026 04:34:30 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php:57
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php on line 57
[09-Aug-2026 04:34:31 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php:22
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php on line 22
[09-Aug-2026 04:34:31 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php:64
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php on line 64
[13-Aug-2026 01:34:55 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php:22
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php on line 22
[13-Aug-2026 01:34:56 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php:57
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php on line 57
[13-Aug-2026 01:34:57 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php:64
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php on line 64
[13-Aug-2026 05:30:20 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php:57
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleImageGenerationModel.php on line 57
[13-Aug-2026 05:30:21 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModel" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php:64
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleTextGenerationModel.php on line 64
[13-Aug-2026 05:30:22 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiBasedModelMetadataDirectory" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php:22
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Providers/OpenAiCompatibleImplementation/AbstractOpenAiCompatibleModelMetadataDirectory.php on line 22
PKx�]�L�0M0MTools/DTO/error_lognu�[���[30-May-2026 09:48:36 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php on line 20
[30-May-2026 09:48:36 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php on line 23
[30-May-2026 09:48:36 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php on line 20
[30-May-2026 09:48:36 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php on line 19
[11-Jun-2026 06:22:23 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php on line 20
[11-Jun-2026 06:22:23 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php on line 23
[11-Jun-2026 06:22:24 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php on line 20
[11-Jun-2026 06:22:24 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php on line 19
[20-Jun-2026 10:36:43 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php on line 20
[20-Jun-2026 10:36:43 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php on line 23
[20-Jun-2026 10:36:43 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php on line 20
[20-Jun-2026 10:36:43 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php on line 19
[21-Jun-2026 10:36:34 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php on line 20
[21-Jun-2026 10:36:34 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php on line 23
[21-Jun-2026 10:36:34 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php on line 20
[21-Jun-2026 10:36:34 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php on line 19
[10-Jul-2026 02:45:51 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php on line 20
[10-Jul-2026 02:45:54 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php on line 23
[10-Jul-2026 02:45:55 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php on line 20
[10-Jul-2026 02:45:56 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php on line 19
[17-Jul-2026 21:09:36 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php on line 20
[17-Jul-2026 21:09:37 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php on line 23
[17-Jul-2026 21:09:38 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php on line 20
[17-Jul-2026 21:09:40 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php on line 19
[18-Jul-2026 11:45:45 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php on line 20
[18-Jul-2026 11:45:56 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php on line 23
[18-Jul-2026 11:45:57 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php on line 20
[18-Jul-2026 11:46:00 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php on line 19
[24-Jul-2026 07:01:39 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php on line 20
[24-Jul-2026 07:01:40 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php on line 23
[24-Jul-2026 07:01:40 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php on line 20
[24-Jul-2026 07:01:40 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php on line 19
[02-Aug-2026 03:19:52 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php on line 20
[02-Aug-2026 03:19:52 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php on line 23
[02-Aug-2026 03:19:53 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php on line 20
[02-Aug-2026 03:19:53 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php on line 19
[02-Aug-2026 04:44:55 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php on line 20
[02-Aug-2026 04:44:56 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php on line 23
[02-Aug-2026 04:44:58 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php on line 19
[02-Aug-2026 04:45:03 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php on line 20
[09-Aug-2026 04:34:34 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php on line 20
[09-Aug-2026 04:34:35 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php on line 23
[09-Aug-2026 04:34:35 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php on line 20
[09-Aug-2026 04:34:35 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php on line 19
[13-Aug-2026 01:35:06 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php on line 19
[13-Aug-2026 01:35:08 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php on line 20
[13-Aug-2026 01:35:10 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php on line 23
[13-Aug-2026 01:35:11 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php on line 20
[13-Aug-2026 05:31:46 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionResponse.php on line 20
[13-Aug-2026 05:31:48 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php:20
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionCall.php on line 20
[13-Aug-2026 05:31:48 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/FunctionDeclaration.php on line 23
[13-Aug-2026 05:31:50 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php:19
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Tools/DTO/WebSearch.php on line 19
PKx�]�;_:�9�9Results/DTO/error_lognu�[���[30-May-2026 09:48:31 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/Candidate.php:24
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/Candidate.php on line 24
[30-May-2026 09:48:31 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php:38
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php on line 38
[30-May-2026 09:48:31 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php:27
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php on line 27
[11-Jun-2026 06:22:16 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/Candidate.php:24
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/Candidate.php on line 24
[11-Jun-2026 06:22:16 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php:38
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php on line 38
[11-Jun-2026 06:22:17 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php:27
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php on line 27
[20-Jun-2026 10:36:41 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/Candidate.php:24
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/Candidate.php on line 24
[20-Jun-2026 10:36:41 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php:38
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php on line 38
[20-Jun-2026 10:36:41 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php:27
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php on line 27
[21-Jun-2026 10:36:32 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/Candidate.php:24
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/Candidate.php on line 24
[21-Jun-2026 10:36:32 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php:38
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php on line 38
[21-Jun-2026 10:36:33 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php:27
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php on line 27
[10-Jul-2026 02:45:38 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/Candidate.php:24
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/Candidate.php on line 24
[10-Jul-2026 02:45:41 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php:38
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php on line 38
[10-Jul-2026 02:45:43 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php:27
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php on line 27
[17-Jul-2026 21:09:24 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/Candidate.php:24
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/Candidate.php on line 24
[17-Jul-2026 21:09:25 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php:38
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php on line 38
[17-Jul-2026 21:09:26 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php:27
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php on line 27
[18-Jul-2026 11:45:26 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/Candidate.php:24
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/Candidate.php on line 24
[18-Jul-2026 11:45:27 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php:38
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php on line 38
[18-Jul-2026 11:45:31 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php:27
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php on line 27
[24-Jul-2026 07:01:38 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/Candidate.php:24
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/Candidate.php on line 24
[24-Jul-2026 07:01:38 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php:38
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php on line 38
[24-Jul-2026 07:01:38 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php:27
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php on line 27
[02-Aug-2026 03:19:51 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/Candidate.php:24
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/Candidate.php on line 24
[02-Aug-2026 03:19:51 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php:38
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php on line 38
[02-Aug-2026 03:19:51 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php:27
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php on line 27
[02-Aug-2026 04:45:22 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php:38
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php on line 38
[02-Aug-2026 04:45:29 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php:27
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php on line 27
[02-Aug-2026 04:45:31 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/Candidate.php:24
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/Candidate.php on line 24
[09-Aug-2026 04:34:33 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/Candidate.php:24
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/Candidate.php on line 24
[09-Aug-2026 04:34:33 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php:38
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php on line 38
[09-Aug-2026 04:34:33 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php:27
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php on line 27
[13-Aug-2026 01:32:48 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php:27
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php on line 27
[13-Aug-2026 01:32:50 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php:38
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php on line 38
[13-Aug-2026 01:32:53 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/Candidate.php:24
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/Candidate.php on line 24
[13-Aug-2026 05:32:09 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/Candidate.php:24
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/Candidate.php on line 24
[13-Aug-2026 05:32:11 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php:38
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/GenerativeAiResult.php on line 38
[13-Aug-2026 05:32:12 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractDataTransferObject" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php:27
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/DTO/TokenUsage.php on line 27
PKx�]١���Results/Enums/error_lognu�[���[30-May-2026 09:48:33 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php on line 23
[11-Jun-2026 06:22:19 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php on line 23
[20-Jun-2026 10:36:42 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php on line 23
[21-Jun-2026 10:36:33 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php on line 23
[10-Jul-2026 02:45:46 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php on line 23
[17-Jul-2026 21:09:29 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php on line 23
[18-Jul-2026 11:45:37 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php on line 23
[24-Jul-2026 07:01:39 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php on line 23
[02-Aug-2026 03:19:52 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php on line 23
[09-Aug-2026 04:34:34 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php on line 23
[13-Aug-2026 01:32:45 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php on line 23
[13-Aug-2026 05:32:07 UTC] PHP Fatal error:  Uncaught Error: Class "WordPress\AiClient\Common\AbstractEnum" not found in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php:23
Stack trace:
#0 {main}
  thrown in /home/boxelikax/public_html/fleetrs.es/wp-includes/php-ai-client/src/Results/Enums/FinishReasonEnum.php on line 23
PKQ�]�v^^FulfilledPromise.phpnu�[���<?php

namespace React\Promise;

/**
 * @deprecated 2.8.0 External usage of FulfilledPromise is deprecated, use `resolve()` instead.
 */
class FulfilledPromise implements ExtendedPromiseInterface, CancellablePromiseInterface
{
    private $value;

    public function __construct($value = null)
    {
        if ($value instanceof PromiseInterface) {
            throw new \InvalidArgumentException('You cannot create React\Promise\FulfilledPromise with a promise. Use React\Promise\resolve($promiseOrValue) instead.');
        }

        $this->value = $value;
    }

    public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
    {
        if (null === $onFulfilled) {
            return $this;
        }

        try {
            return resolve($onFulfilled($this->value));
        } catch (\Throwable $exception) {
            return new RejectedPromise($exception);
        } catch (\Exception $exception) {
            return new RejectedPromise($exception);
        }
    }

    public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
    {
        if (null === $onFulfilled) {
            return;
        }

        $result = $onFulfilled($this->value);

        if ($result instanceof ExtendedPromiseInterface) {
            $result->done();
        }
    }

    public function otherwise(callable $onRejected)
    {
        return $this;
    }

    public function always(callable $onFulfilledOrRejected)
    {
        return $this->then(function ($value) use ($onFulfilledOrRejected) {
            return resolve($onFulfilledOrRejected())->then(function () use ($value) {
                return $value;
            });
        });
    }

    public function progress(callable $onProgress)
    {
        return $this;
    }

    public function cancel()
    {
    }
}
PKQ�]�\lb��CancellablePromiseInterface.phpnu�[���<?php

namespace React\Promise;

interface CancellablePromiseInterface extends PromiseInterface
{
    /**
     * The `cancel()` method notifies the creator of the promise that there is no
     * further interest in the results of the operation.
     *
     * Once a promise is settled (either fulfilled or rejected), calling `cancel()` on
     * a promise has no effect.
     *
     * @return void
     */
    public function cancel();
}
PKQ�]g4�eeUnhandledRejectionException.phpnu�[���<?php

namespace React\Promise;

class UnhandledRejectionException extends \RuntimeException
{
    private $reason;

    public static function resolve($reason)
    {
        if ($reason instanceof \Exception || $reason instanceof \Throwable) {
            return $reason;
        }

        return new static($reason);
    }

    public function __construct($reason)
    {
        $this->reason = $reason;

        $message = \sprintf('Unhandled Rejection: %s', \json_encode($reason));

        parent::__construct($message, 0);
    }

    public function getReason()
    {
        return $this->reason;
    }
}
PKQ�]?q^^Exception/LengthException.phpnu�[���<?php

namespace React\Promise\Exception;

class LengthException extends \LengthException
{
}
PKQ�]�|�aafunctions_include.phpnu�[���<?php

if (!\function_exists('React\Promise\resolve')) {
    require __DIR__.'/functions.php';
}
PKQ�]x=���7�7
functions.phpnu�[���<?php

namespace React\Promise;

/**
 * Creates a promise for the supplied `$promiseOrValue`.
 *
 * If `$promiseOrValue` is a value, it will be the resolution value of the
 * returned promise.
 *
 * If `$promiseOrValue` is a thenable (any object that provides a `then()` method),
 * a trusted promise that follows the state of the thenable is returned.
 *
 * If `$promiseOrValue` is a promise, it will be returned as is.
 *
 * @param mixed $promiseOrValue
 * @return PromiseInterface
 */
function resolve($promiseOrValue = null)
{
    if ($promiseOrValue instanceof ExtendedPromiseInterface) {
        return $promiseOrValue;
    }

    // Check is_object() first to avoid method_exists() triggering
    // class autoloaders if $promiseOrValue is a string.
    if (\is_object($promiseOrValue) && \method_exists($promiseOrValue, 'then')) {
        $canceller = null;

        if (\method_exists($promiseOrValue, 'cancel')) {
            $canceller = [$promiseOrValue, 'cancel'];
        }

        return new Promise(function ($resolve, $reject, $notify) use ($promiseOrValue) {
            $promiseOrValue->then($resolve, $reject, $notify);
        }, $canceller);
    }

    return new FulfilledPromise($promiseOrValue);
}

/**
 * Creates a rejected promise for the supplied `$promiseOrValue`.
 *
 * If `$promiseOrValue` is a value, it will be the rejection value of the
 * returned promise.
 *
 * If `$promiseOrValue` is a promise, its completion value will be the rejected
 * value of the returned promise.
 *
 * This can be useful in situations where you need to reject a promise without
 * throwing an exception. For example, it allows you to propagate a rejection with
 * the value of another promise.
 *
 * @param mixed $promiseOrValue
 * @return PromiseInterface
 */
function reject($promiseOrValue = null)
{
    if ($promiseOrValue instanceof PromiseInterface) {
        return resolve($promiseOrValue)->then(function ($value) {
            return new RejectedPromise($value);
        });
    }

    return new RejectedPromise($promiseOrValue);
}

/**
 * Returns a promise that will resolve only once all the items in
 * `$promisesOrValues` have resolved. The resolution value of the returned promise
 * will be an array containing the resolution values of each of the items in
 * `$promisesOrValues`.
 *
 * @param array $promisesOrValues
 * @return PromiseInterface
 */
function all($promisesOrValues)
{
    return map($promisesOrValues, function ($val) {
        return $val;
    });
}

/**
 * Initiates a competitive race that allows one winner. Returns a promise which is
 * resolved in the same way the first settled promise resolves.
 *
 * The returned promise will become **infinitely pending** if  `$promisesOrValues`
 * contains 0 items.
 *
 * @param array $promisesOrValues
 * @return PromiseInterface
 */
function race($promisesOrValues)
{
    $cancellationQueue = new CancellationQueue();
    $cancellationQueue->enqueue($promisesOrValues);

    return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $cancellationQueue) {
        resolve($promisesOrValues)
            ->done(function ($array) use ($cancellationQueue, $resolve, $reject, $notify) {
                if (!is_array($array) || !$array) {
                    $resolve();
                    return;
                }

                foreach ($array as $promiseOrValue) {
                    $cancellationQueue->enqueue($promiseOrValue);

                    resolve($promiseOrValue)
                        ->done($resolve, $reject, $notify);
                }
            }, $reject, $notify);
    }, $cancellationQueue);
}

/**
 * Returns a promise that will resolve when any one of the items in
 * `$promisesOrValues` resolves. The resolution value of the returned promise
 * will be the resolution value of the triggering item.
 *
 * The returned promise will only reject if *all* items in `$promisesOrValues` are
 * rejected. The rejection value will be an array of all rejection reasons.
 *
 * The returned promise will also reject with a `React\Promise\Exception\LengthException`
 * if `$promisesOrValues` contains 0 items.
 *
 * @param array $promisesOrValues
 * @return PromiseInterface
 */
function any($promisesOrValues)
{
    return some($promisesOrValues, 1)
        ->then(function ($val) {
            return \array_shift($val);
        });
}

/**
 * Returns a promise that will resolve when `$howMany` of the supplied items in
 * `$promisesOrValues` resolve. The resolution value of the returned promise
 * will be an array of length `$howMany` containing the resolution values of the
 * triggering items.
 *
 * The returned promise will reject if it becomes impossible for `$howMany` items
 * to resolve (that is, when `(count($promisesOrValues) - $howMany) + 1` items
 * reject). The rejection value will be an array of
 * `(count($promisesOrValues) - $howMany) + 1` rejection reasons.
 *
 * The returned promise will also reject with a `React\Promise\Exception\LengthException`
 * if `$promisesOrValues` contains less items than `$howMany`.
 *
 * @param array $promisesOrValues
 * @param int $howMany
 * @return PromiseInterface
 */
function some($promisesOrValues, $howMany)
{
    $cancellationQueue = new CancellationQueue();
    $cancellationQueue->enqueue($promisesOrValues);

    return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $howMany, $cancellationQueue) {
        resolve($promisesOrValues)
            ->done(function ($array) use ($howMany, $cancellationQueue, $resolve, $reject, $notify) {
                if (!\is_array($array) || $howMany < 1) {
                    $resolve([]);
                    return;
                }

                $len = \count($array);

                if ($len < $howMany) {
                    throw new Exception\LengthException(
                        \sprintf(
                            'Input array must contain at least %d item%s but contains only %s item%s.',
                            $howMany,
                            1 === $howMany ? '' : 's',
                            $len,
                            1 === $len ? '' : 's'
                        )
                    );
                }

                $toResolve = $howMany;
                $toReject  = ($len - $toResolve) + 1;
                $values    = [];
                $reasons   = [];

                foreach ($array as $i => $promiseOrValue) {
                    $fulfiller = function ($val) use ($i, &$values, &$toResolve, $toReject, $resolve) {
                        if ($toResolve < 1 || $toReject < 1) {
                            return;
                        }

                        $values[$i] = $val;

                        if (0 === --$toResolve) {
                            $resolve($values);
                        }
                    };

                    $rejecter = function ($reason) use ($i, &$reasons, &$toReject, $toResolve, $reject) {
                        if ($toResolve < 1 || $toReject < 1) {
                            return;
                        }

                        $reasons[$i] = $reason;

                        if (0 === --$toReject) {
                            $reject($reasons);
                        }
                    };

                    $cancellationQueue->enqueue($promiseOrValue);

                    resolve($promiseOrValue)
                        ->done($fulfiller, $rejecter, $notify);
                }
            }, $reject, $notify);
    }, $cancellationQueue);
}

/**
 * Traditional map function, similar to `array_map()`, but allows input to contain
 * promises and/or values, and `$mapFunc` may return either a value or a promise.
 *
 * The map function receives each item as argument, where item is a fully resolved
 * value of a promise or value in `$promisesOrValues`.
 *
 * @param array $promisesOrValues
 * @param callable $mapFunc
 * @return PromiseInterface
 */
function map($promisesOrValues, callable $mapFunc)
{
    $cancellationQueue = new CancellationQueue();
    $cancellationQueue->enqueue($promisesOrValues);

    return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $mapFunc, $cancellationQueue) {
        resolve($promisesOrValues)
            ->done(function ($array) use ($mapFunc, $cancellationQueue, $resolve, $reject, $notify) {
                if (!\is_array($array) || !$array) {
                    $resolve([]);
                    return;
                }

                $toResolve = \count($array);
                $values    = [];

                foreach ($array as $i => $promiseOrValue) {
                    $cancellationQueue->enqueue($promiseOrValue);
                    $values[$i] = null;

                    resolve($promiseOrValue)
                        ->then($mapFunc)
                        ->done(
                            function ($mapped) use ($i, &$values, &$toResolve, $resolve) {
                                $values[$i] = $mapped;

                                if (0 === --$toResolve) {
                                    $resolve($values);
                                }
                            },
                            $reject,
                            $notify
                        );
                }
            }, $reject, $notify);
    }, $cancellationQueue);
}

/**
 * Traditional reduce function, similar to `array_reduce()`, but input may contain
 * promises and/or values, and `$reduceFunc` may return either a value or a
 * promise, *and* `$initialValue` may be a promise or a value for the starting
 * value.
 *
 * @param array $promisesOrValues
 * @param callable $reduceFunc
 * @param mixed $initialValue
 * @return PromiseInterface
 */
function reduce($promisesOrValues, callable $reduceFunc, $initialValue = null)
{
    $cancellationQueue = new CancellationQueue();
    $cancellationQueue->enqueue($promisesOrValues);

    return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $reduceFunc, $initialValue, $cancellationQueue) {
        resolve($promisesOrValues)
            ->done(function ($array) use ($reduceFunc, $initialValue, $cancellationQueue, $resolve, $reject, $notify) {
                if (!\is_array($array)) {
                    $array = [];
                }

                $total = \count($array);
                $i = 0;

                // Wrap the supplied $reduceFunc with one that handles promises and then
                // delegates to the supplied.
                $wrappedReduceFunc = function ($current, $val) use ($reduceFunc, $cancellationQueue, $total, &$i) {
                    $cancellationQueue->enqueue($val);

                    return $current
                        ->then(function ($c) use ($reduceFunc, $total, &$i, $val) {
                            return resolve($val)
                                ->then(function ($value) use ($reduceFunc, $total, &$i, $c) {
                                    return $reduceFunc($c, $value, $i++, $total);
                                });
                        });
                };

                $cancellationQueue->enqueue($initialValue);

                \array_reduce($array, $wrappedReduceFunc, resolve($initialValue))
                    ->done($resolve, $reject, $notify);
            }, $reject, $notify);
    }, $cancellationQueue);
}

/**
 * @internal
 */
function _checkTypehint(callable $callback, $object)
{
    if (!\is_object($object)) {
        return true;
    }

    if (\is_array($callback)) {
        $callbackReflection = new \ReflectionMethod($callback[0], $callback[1]);
    } elseif (\is_object($callback) && !$callback instanceof \Closure) {
        $callbackReflection = new \ReflectionMethod($callback, '__invoke');
    } else {
        $callbackReflection = new \ReflectionFunction($callback);
    }

    $parameters = $callbackReflection->getParameters();

    if (!isset($parameters[0])) {
        return true;
    }

    $expectedException = $parameters[0];

    // PHP before v8 used an easy API:
    if (\PHP_VERSION_ID < 70100 || \defined('HHVM_VERSION')) {
        if (!$expectedException->getClass()) {
            return true;
        }

        return $expectedException->getClass()->isInstance($object);
    }

    // Extract the type of the argument and handle different possibilities
    $type = $expectedException->getType();

    $isTypeUnion = true;
    $types = [];

    switch (true) {
        case $type === null:
            break;
        case $type instanceof \ReflectionNamedType:
            $types = [$type];
            break;
        case $type instanceof \ReflectionIntersectionType:
            $isTypeUnion = false;
        case $type instanceof \ReflectionUnionType;
            $types = $type->getTypes();
            break;
        default:
            throw new \LogicException('Unexpected return value of ReflectionParameter::getType');
    }

    // If there is no type restriction, it matches
    if (empty($types)) {
        return true;
    }

    foreach ($types as $type) {

        if ($type instanceof \ReflectionIntersectionType) {
            foreach ($type->getTypes() as $typeToMatch) {
                if (!($matches = ($typeToMatch->isBuiltin() && \gettype($object) === $typeToMatch->getName())
                    || (new \ReflectionClass($typeToMatch->getName()))->isInstance($object))) {
                    break;
                }
            }
        } else {
            $matches = ($type->isBuiltin() && \gettype($object) === $type->getName())
                || (new \ReflectionClass($type->getName()))->isInstance($object);
        }

        // If we look for a single match (union), we can return early on match
        // If we look for a full match (intersection), we can return early on mismatch
        if ($matches) {
            if ($isTypeUnion) {
                return true;
            }
        } else {
            if (!$isTypeUnion) {
                return false;
            }
        }
    }

    // If we look for a single match (union) and did not return early, we matched no type and are false
    // If we look for a full match (intersection) and did not return early, we matched all types and are true
    return $isTypeUnion ? false : true;
}
PKQ�]�M�J��LazyPromise.phpnu�[���<?php

namespace React\Promise;

/**
 * @deprecated 2.8.0 LazyPromise is deprecated and should not be used anymore.
 */
class LazyPromise implements ExtendedPromiseInterface, CancellablePromiseInterface
{
    private $factory;
    private $promise;

    public function __construct(callable $factory)
    {
        $this->factory = $factory;
    }

    public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
    {
        return $this->promise()->then($onFulfilled, $onRejected, $onProgress);
    }

    public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
    {
        return $this->promise()->done($onFulfilled, $onRejected, $onProgress);
    }

    public function otherwise(callable $onRejected)
    {
        return $this->promise()->otherwise($onRejected);
    }

    public function always(callable $onFulfilledOrRejected)
    {
        return $this->promise()->always($onFulfilledOrRejected);
    }

    public function progress(callable $onProgress)
    {
        return $this->promise()->progress($onProgress);
    }

    public function cancel()
    {
        return $this->promise()->cancel();
    }

    /**
     * @internal
     * @see Promise::settle()
     */
    public function promise()
    {
        if (null === $this->promise) {
            try {
                $this->promise = resolve(\call_user_func($this->factory));
            } catch (\Throwable $exception) {
                $this->promise = new RejectedPromise($exception);
            } catch (\Exception $exception) {
                $this->promise = new RejectedPromise($exception);
            }
        }

        return $this->promise;
    }
}
PKQ�]��Iv
v
ExtendedPromiseInterface.phpnu�[���<?php

namespace React\Promise;

interface ExtendedPromiseInterface extends PromiseInterface
{
    /**
     * Consumes the promise's ultimate value if the promise fulfills, or handles the
     * ultimate error.
     *
     * It will cause a fatal error if either `$onFulfilled` or
     * `$onRejected` throw or return a rejected promise.
     *
     * Since the purpose of `done()` is consumption rather than transformation,
     * `done()` always returns `null`.
     *
     * @param callable|null $onFulfilled
     * @param callable|null $onRejected
     * @param callable|null $onProgress This argument is deprecated and should not be used anymore.
     * @return void
     */
    public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null);

    /**
     * Registers a rejection handler for promise. It is a shortcut for:
     *
     * ```php
     * $promise->then(null, $onRejected);
     * ```
     *
     * Additionally, you can type hint the `$reason` argument of `$onRejected` to catch
     * only specific errors.
     *
     * @param callable $onRejected
     * @return ExtendedPromiseInterface
     */
    public function otherwise(callable $onRejected);

    /**
     * Allows you to execute "cleanup" type tasks in a promise chain.
     *
     * It arranges for `$onFulfilledOrRejected` to be called, with no arguments,
     * when the promise is either fulfilled or rejected.
     *
     * * If `$promise` fulfills, and `$onFulfilledOrRejected` returns successfully,
     *    `$newPromise` will fulfill with the same value as `$promise`.
     * * If `$promise` fulfills, and `$onFulfilledOrRejected` throws or returns a
     *    rejected promise, `$newPromise` will reject with the thrown exception or
     *    rejected promise's reason.
     * * If `$promise` rejects, and `$onFulfilledOrRejected` returns successfully,
     *    `$newPromise` will reject with the same reason as `$promise`.
     * * If `$promise` rejects, and `$onFulfilledOrRejected` throws or returns a
     *    rejected promise, `$newPromise` will reject with the thrown exception or
     *    rejected promise's reason.
     *
     * `always()` behaves similarly to the synchronous finally statement. When combined
     * with `otherwise()`, `always()` allows you to write code that is similar to the familiar
     * synchronous catch/finally pair.
     *
     * Consider the following synchronous code:
     *
     * ```php
     * try {
     *     return doSomething();
     * } catch(\Exception $e) {
     *     return handleError($e);
     * } finally {
     *     cleanup();
     * }
     * ```
     *
     * Similar asynchronous code (with `doSomething()` that returns a promise) can be
     * written:
     *
     * ```php
     * return doSomething()
     *     ->otherwise('handleError')
     *     ->always('cleanup');
     * ```
     *
     * @param callable $onFulfilledOrRejected
     * @return ExtendedPromiseInterface
     */
    public function always(callable $onFulfilledOrRejected);

    /**
     * Registers a handler for progress updates from promise. It is a shortcut for:
     *
     * ```php
     * $promise->then(null, null, $onProgress);
     * ```
     *
     * @param callable $onProgress
     * @return ExtendedPromiseInterface
     * @deprecated 2.6.0 Progress support is deprecated and should not be used anymore.
     */
    public function progress(callable $onProgress);
}
PKQ�]�M�0��RejectedPromise.phpnu�[���<?php

namespace React\Promise;

/**
 * @deprecated 2.8.0 External usage of RejectedPromise is deprecated, use `reject()` instead.
 */
class RejectedPromise implements ExtendedPromiseInterface, CancellablePromiseInterface
{
    private $reason;

    public function __construct($reason = null)
    {
        if ($reason instanceof PromiseInterface) {
            throw new \InvalidArgumentException('You cannot create React\Promise\RejectedPromise with a promise. Use React\Promise\reject($promiseOrValue) instead.');
        }

        $this->reason = $reason;
    }

    public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
    {
        if (null === $onRejected) {
            return $this;
        }

        try {
            return resolve($onRejected($this->reason));
        } catch (\Throwable $exception) {
            return new RejectedPromise($exception);
        } catch (\Exception $exception) {
            return new RejectedPromise($exception);
        }
    }

    public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
    {
        if (null === $onRejected) {
            throw UnhandledRejectionException::resolve($this->reason);
        }

        $result = $onRejected($this->reason);

        if ($result instanceof self) {
            throw UnhandledRejectionException::resolve($result->reason);
        }

        if ($result instanceof ExtendedPromiseInterface) {
            $result->done();
        }
    }

    public function otherwise(callable $onRejected)
    {
        if (!_checkTypehint($onRejected, $this->reason)) {
            return $this;
        }

        return $this->then(null, $onRejected);
    }

    public function always(callable $onFulfilledOrRejected)
    {
        return $this->then(null, function ($reason) use ($onFulfilledOrRejected) {
            return resolve($onFulfilledOrRejected())->then(function () use ($reason) {
                return new RejectedPromise($reason);
            });
        });
    }

    public function progress(callable $onProgress)
    {
        return $this;
    }

    public function cancel()
    {
    }
}
PKQ�]^�
��PromiseInterface.phpnu�[���<?php

namespace React\Promise;

interface PromiseInterface
{
    /**
     * Transforms a promise's value by applying a function to the promise's fulfillment
     * or rejection value. Returns a new promise for the transformed result.
     *
     * The `then()` method registers new fulfilled and rejection handlers with a promise
     * (all parameters are optional):
     *
     *  * `$onFulfilled` will be invoked once the promise is fulfilled and passed
     *     the result as the first argument.
     *  * `$onRejected` will be invoked once the promise is rejected and passed the
     *     reason as the first argument.
     *  * `$onProgress` (deprecated) will be invoked whenever the producer of the promise
     *     triggers progress notifications and passed a single argument (whatever it
     *     wants) to indicate progress.
     *
     * It returns a new promise that will fulfill with the return value of either
     * `$onFulfilled` or `$onRejected`, whichever is called, or will reject with
     * the thrown exception if either throws.
     *
     * A promise makes the following guarantees about handlers registered in
     * the same call to `then()`:
     *
     *  1. Only one of `$onFulfilled` or `$onRejected` will be called,
     *      never both.
     *  2. `$onFulfilled` and `$onRejected` will never be called more
     *      than once.
     *  3. `$onProgress` (deprecated) may be called multiple times.
     *
     * @param callable|null $onFulfilled
     * @param callable|null $onRejected
     * @param callable|null $onProgress This argument is deprecated and should not be used anymore.
     * @return PromiseInterface
     */
    public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null);
}
PKQ�]w�;��PromisorInterface.phpnu�[���<?php

namespace React\Promise;

interface PromisorInterface
{
    /**
     * Returns the promise of the deferred.
     *
     * @return PromiseInterface
     */
    public function promise();
}
PKQ�]
̼�Deferred.phpnu�[���<?php

namespace React\Promise;

class Deferred implements PromisorInterface
{
    private $promise;
    private $resolveCallback;
    private $rejectCallback;
    private $notifyCallback;
    private $canceller;

    public function __construct(callable $canceller = null)
    {
        $this->canceller = $canceller;
    }

    public function promise()
    {
        if (null === $this->promise) {
            $this->promise = new Promise(function ($resolve, $reject, $notify) {
                $this->resolveCallback = $resolve;
                $this->rejectCallback  = $reject;
                $this->notifyCallback  = $notify;
            }, $this->canceller);
            $this->canceller = null;
        }

        return $this->promise;
    }

    public function resolve($value = null)
    {
        $this->promise();

        \call_user_func($this->resolveCallback, $value);
    }

    public function reject($reason = null)
    {
        $this->promise();

        \call_user_func($this->rejectCallback, $reason);
    }

    /**
     * @deprecated 2.6.0 Progress support is deprecated and should not be used anymore.
     * @param mixed $update
     */
    public function notify($update = null)
    {
        $this->promise();

        \call_user_func($this->notifyCallback, $update);
    }

    /**
     * @deprecated 2.2.0
     * @see Deferred::notify()
     */
    public function progress($update = null)
    {
        $this->notify($update);
    }
}
PKQ�]��m�"�"Promise.phpnu�[���<?php

namespace React\Promise;

class Promise implements ExtendedPromiseInterface, CancellablePromiseInterface
{
    private $canceller;
    private $result;

    private $handlers = [];
    private $progressHandlers = [];

    private $requiredCancelRequests = 0;
    private $cancelRequests = 0;

    public function __construct(callable $resolver, callable $canceller = null)
    {
        $this->canceller = $canceller;

        // Explicitly overwrite arguments with null values before invoking
        // resolver function. This ensure that these arguments do not show up
        // in the stack trace in PHP 7+ only.
        $cb = $resolver;
        $resolver = $canceller = null;
        $this->call($cb);
    }

    public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
    {
        if (null !== $this->result) {
            return $this->result->then($onFulfilled, $onRejected, $onProgress);
        }

        if (null === $this->canceller) {
            return new static($this->resolver($onFulfilled, $onRejected, $onProgress));
        }

        // This promise has a canceller, so we create a new child promise which
        // has a canceller that invokes the parent canceller if all other
        // followers are also cancelled. We keep a reference to this promise
        // instance for the static canceller function and clear this to avoid
        // keeping a cyclic reference between parent and follower.
        $parent = $this;
        ++$parent->requiredCancelRequests;

        return new static(
            $this->resolver($onFulfilled, $onRejected, $onProgress),
            static function () use (&$parent) {
                if (++$parent->cancelRequests >= $parent->requiredCancelRequests) {
                    $parent->cancel();
                }

                $parent = null;
            }
        );
    }

    public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
    {
        if (null !== $this->result) {
            return $this->result->done($onFulfilled, $onRejected, $onProgress);
        }

        $this->handlers[] = static function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected) {
            $promise
                ->done($onFulfilled, $onRejected);
        };

        if ($onProgress) {
            $this->progressHandlers[] = $onProgress;
        }
    }

    public function otherwise(callable $onRejected)
    {
        return $this->then(null, static function ($reason) use ($onRejected) {
            if (!_checkTypehint($onRejected, $reason)) {
                return new RejectedPromise($reason);
            }

            return $onRejected($reason);
        });
    }

    public function always(callable $onFulfilledOrRejected)
    {
        return $this->then(static function ($value) use ($onFulfilledOrRejected) {
            return resolve($onFulfilledOrRejected())->then(function () use ($value) {
                return $value;
            });
        }, static function ($reason) use ($onFulfilledOrRejected) {
            return resolve($onFulfilledOrRejected())->then(function () use ($reason) {
                return new RejectedPromise($reason);
            });
        });
    }

    public function progress(callable $onProgress)
    {
        return $this->then(null, null, $onProgress);
    }

    public function cancel()
    {
        if (null === $this->canceller || null !== $this->result) {
            return;
        }

        $canceller = $this->canceller;
        $this->canceller = null;

        $this->call($canceller);
    }

    private function resolver(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null)
    {
        return function ($resolve, $reject, $notify) use ($onFulfilled, $onRejected, $onProgress) {
            if ($onProgress) {
                $progressHandler = static function ($update) use ($notify, $onProgress) {
                    try {
                        $notify($onProgress($update));
                    } catch (\Throwable $e) {
                        $notify($e);
                    } catch (\Exception $e) {
                        $notify($e);
                    }
                };
            } else {
                $progressHandler = $notify;
            }

            $this->handlers[] = static function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected, $resolve, $reject, $progressHandler) {
                $promise
                    ->then($onFulfilled, $onRejected)
                    ->done($resolve, $reject, $progressHandler);
            };

            $this->progressHandlers[] = $progressHandler;
        };
    }

    private function reject($reason = null)
    {
        if (null !== $this->result) {
            return;
        }

        $this->settle(reject($reason));
    }

    private function settle(ExtendedPromiseInterface $promise)
    {
        $promise = $this->unwrap($promise);

        if ($promise === $this) {
            $promise = new RejectedPromise(
                new \LogicException('Cannot resolve a promise with itself.')
            );
        }

        $handlers = $this->handlers;

        $this->progressHandlers = $this->handlers = [];
        $this->result = $promise;
        $this->canceller = null;

        foreach ($handlers as $handler) {
            $handler($promise);
        }
    }

    private function unwrap($promise)
    {
        $promise = $this->extract($promise);

        while ($promise instanceof self && null !== $promise->result) {
            $promise = $this->extract($promise->result);
        }

        return $promise;
    }

    private function extract($promise)
    {
        if ($promise instanceof LazyPromise) {
            $promise = $promise->promise();
        }

        return $promise;
    }

    private function call(callable $cb)
    {
        // Explicitly overwrite argument with null value. This ensure that this
        // argument does not show up in the stack trace in PHP 7+ only.
        $callback = $cb;
        $cb = null;

        // Use reflection to inspect number of arguments expected by this callback.
        // We did some careful benchmarking here: Using reflection to avoid unneeded
        // function arguments is actually faster than blindly passing them.
        // Also, this helps avoiding unnecessary function arguments in the call stack
        // if the callback creates an Exception (creating garbage cycles).
        if (\is_array($callback)) {
            $ref = new \ReflectionMethod($callback[0], $callback[1]);
        } elseif (\is_object($callback) && !$callback instanceof \Closure) {
            $ref = new \ReflectionMethod($callback, '__invoke');
        } else {
            $ref = new \ReflectionFunction($callback);
        }
        $args = $ref->getNumberOfParameters();

        try {
            if ($args === 0) {
                $callback();
            } else {
                // Keep references to this promise instance for the static resolve/reject functions.
                // By using static callbacks that are not bound to this instance
                // and passing the target promise instance by reference, we can
                // still execute its resolving logic and still clear this
                // reference when settling the promise. This helps avoiding
                // garbage cycles if any callback creates an Exception.
                // These assumptions are covered by the test suite, so if you ever feel like
                // refactoring this, go ahead, any alternative suggestions are welcome!
                $target =& $this;
                $progressHandlers =& $this->progressHandlers;

                $callback(
                    static function ($value = null) use (&$target) {
                        if ($target !== null) {
                            $target->settle(resolve($value));
                            $target = null;
                        }
                    },
                    static function ($reason = null) use (&$target) {
                        if ($target !== null) {
                            $target->reject($reason);
                            $target = null;
                        }
                    },
                    static function ($update = null) use (&$progressHandlers) {
                        foreach ($progressHandlers as $handler) {
                            $handler($update);
                        }
                    }
                );
            }
        } catch (\Throwable $e) {
            $target = null;
            $this->reject($e);
        } catch (\Exception $e) {
            $target = null;
            $this->reject($e);
        }
    }
}
PKQ�]���nuuCancellationQueue.phpnu�[���<?php

namespace React\Promise;

class CancellationQueue
{
    private $started = false;
    private $queue = [];

    public function __invoke()
    {
        if ($this->started) {
            return;
        }

        $this->started = true;
        $this->drain();
    }

    public function enqueue($cancellable)
    {
        if (!\is_object($cancellable) || !\method_exists($cancellable, 'then') || !\method_exists($cancellable, 'cancel')) {
            return;
        }

        $length = \array_push($this->queue, $cancellable);

        if ($this->started && 1 === $length) {
            $this->drain();
        }
    }

    private function drain()
    {
        for ($i = key($this->queue); isset($this->queue[$i]); $i++) {
            $cancellable = $this->queue[$i];

            $exception = null;

            try {
                $cancellable->cancel();
            } catch (\Throwable $exception) {
            } catch (\Exception $exception) {
            }

            unset($this->queue[$i]);

            if ($exception) {
                throw $exception;
            }
        }

        $this->queue = [];
    }
}
PK]��qqweb-socket-manager.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module web-socket-manager */

import Base64 from 'js-base64';

/**
 * A web-socket manager.
 */
class WebSocketManager {

    /**
     * @param {module:models/settings} config A config.
     */
    constructor(config) {
        /**
         * @private
         * @type {module:models/settings}
         */
        this.config = config;

        /**
         * @private
         * @type {{category: string, callback: Function}[]}
         */
        this.subscribeQueue = [];

        /**
         * @private
         * @type {boolean}
         */
        this.isConnected = false;

        /**
         * @private
         */
        this.connection = null;

        /**
         * @private
         * @type {string}
         */
        this.url = '';

        /**
         * @private
         * @type {string}
         */
        this.protocolPart = '';

        let url = this.config.get('webSocketUrl');

        if (url) {
            if (url.indexOf('wss://') === 0) {
                this.url = url.substring(6);
                this.protocolPart = 'wss://';
            }
            else {
                this.url = url.substring(5);
                this.protocolPart = 'ws://';
            }
        }
        else {
            let siteUrl = this.config.get('siteUrl') || '';

            if (siteUrl.indexOf('https://') === 0) {
                this.url = siteUrl.substring(8);
                this.protocolPart = 'wss://';
            }
            else {
                this.url = siteUrl.substring(7);
                this.protocolPart = 'ws://';
            }

            if (~this.url.indexOf('/')) {
                this.url = this.url.replace(/\/$/, '');
            }

            let port = this.protocolPart === 'wss://' ? 443 : 8080;

            let si = this.url.indexOf('/');

            if (~si) {
                this.url = this.url.substring(0, si) + ':' + port;
            }
            else {
                this.url += ':' + port;
            }

            if (this.protocolPart === 'wss://') {
                this.url += '/wss';
            }
        }
    }

    /**
     * Connect.
     *
     * @param {string} auth An auth string.
     * @param {string} userId A user ID.
     */
    connect(auth, userId) {
        let authArray = Base64.decode(auth).split(':');

        let authToken = authArray[1];

        let url = this.protocolPart + this.url;

        url += '?authToken=' + authToken + '&userId=' + userId;

        try {
            this.connection = new ab.Session(
                url,
                () => {
                    this.isConnected = true;

                    this.subscribeQueue.forEach(item => {
                        this.subscribe(item.category, item.callback);
                    });

                    this.subscribeQueue = [];
                },
                e => {
                    if (e === ab.CONNECTION_CLOSED) {
                        this.subscribeQueue = [];
                    }

                    if (e === ab.CONNECTION_LOST || e === ab.CONNECTION_UNREACHABLE) {
                        setTimeout(() => this.connect(auth, userId), 3000);
                    }
                },
                {skipSubprotocolCheck: true}
            );
        }
        catch (e) {
            console.error(e.message);

            this.connection = null;
        }
    }

    /**
     * Subscribe to a topic.
     *
     * @param {string} category A topic.
     * @param {Function} callback A callback.
     */
    subscribe(category, callback) {
        if (!this.connection) {
            return;
        }

        if (!this.isConnected) {
            this.subscribeQueue.push({
                category: category,
                callback: callback,
            });

            return;
        }

        try {
            this.connection.subscribe(category, callback);
        }
        catch (e) {
            if (e.message) {
                console.error(e.message);
            }
            else {
                console.error("WebSocket: Could not subscribe to "+category+".");
            }
        }
    }

    /**
     * Unsubscribe.
     *
     * @param {string} category A topic.
     * @param {Function} [callback] A callback.
     */
    unsubscribe(category, callback) {
        if (!this.connection) {
            return;
        }

        this.subscribeQueue = this.subscribeQueue.filter(item => {
            return item.category !== category && item.callback !== callback;
        });

        try {
            this.connection.unsubscribe(category, callback);
        }
        catch (e) {
            if (e.message) {
                console.error(e.message);
            }
            else {
                console.error("WebSocket: Could not unsubscribe from "+category+".");
            }
        }
    }

    /**
     * Close a connection.
     */
    close() {
        if (!this.connection) {
            return;
        }

        try {
            this.connection.close();
        }
        catch (e) {
            console.error(e.message);
        }

        this.isConnected = false;
    }
}

export default WebSocketManager;
PK]�Bρ��metadata.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module metadata */

import {Events} from 'bullbone';

/**
 * Application metadata.
 *
 * @mixes Bull.Events
 */
class Metadata {

    /**
     * Application metadata.
     *
     * @param {module:cache} [cache] A cache.
     */
    constructor(cache) {
        /**
         * @private
         * @type {module:cache|null}
         */
        this.cache = cache || null;

        /**
         * @private
         * @type {Object}
         */
        this.data = {};
    }

    /** @private */
    url = 'Metadata'

    /**
     * Load from cache or the backend (if not yet cached).
     *
     * @param {Function|null} [callback] Deprecated. Use a promise.
     * @param {boolean} [disableCache=false] Bypass cache.
     * @returns {Promise}
     */
    load(callback, disableCache) {
        this.off('sync');

        if (callback) {
            this.once('sync', callback);
        }

        if (!disableCache) {
            if (this.loadFromCache()) {
                this.trigger('sync');

                return new Promise(resolve => resolve());
            }
        }

        return new Promise(resolve => {
            this.fetch()
                .then(() => resolve());
        });
    }

    /**
     * Load from the server.
     *
     * @returns {Promise}
     */
    loadSkipCache() {
        return this.load(null, true);
    }

    /**
     * @private
     * @returns {Promise}
     */
    fetch() {
        return Espo.Ajax.getRequest(this.url)
            .then(data => {
                this.data = data;
                this.storeToCache();
                this.trigger('sync');
            });
    }

    /**
     * Get a value.
     *
     * @param {string[]|string} path A key path.
     * @param {*} [defaultValue] A value to return if not set.
     * @returns {*} Null if not set.
     */
    get(path, defaultValue) {
        defaultValue = defaultValue || null;

        let arr;

        if (Array && Array.isArray && Array.isArray(path)) {
            arr = path;
        }
        else {
            arr = path.split('.');
        }

        let pointer = this.data;
        let result = defaultValue;

        for (var i = 0; i < arr.length; i++) {
            let key = arr[i];

            if (!(key in pointer)) {
                result = defaultValue;

                break;
            }

            if (arr.length - 1 === i) {
                result = pointer[key];
            }

            pointer = pointer[key];
        }

        return result;
    }

    /**
     * @private
     * @returns {boolean|null} True if success.
     */
    loadFromCache() {
        if (this.cache) {
            let cached = this.cache.get('app', 'metadata');

            if (cached) {
                this.data = cached;

                return true;
            }
        }

        return null;
    }

    /** @private */
    storeToCache() {
        if (this.cache) {
            this.cache.set('app', 'metadata', this.data);
        }
    }

    /**
     * Clear cache.
     */
    clearCache() {
        if (!this.cache) {
            return;
        }

        this.cache.clear('app', 'metadata');
    }

    /**
     * Get a scope list.
     *
     * @returns {string[]}
     */
    getScopeList () {
        let scopes = this.get('scopes') || {};
        let scopeList = [];

        for (let scope in scopes) {
            let d = scopes[scope];

            if (d.disabled) {
                continue;
            }

            scopeList.push(scope);
        }

        return scopeList;
    }

    /**
     * Get an object-scope list. An object-scope represents a business entity.
     *
     * @returns {string[]}
     */
    getScopeObjectList () {
        let scopes = this.get('scopes') || {};
        let scopeList = [];

        for (let scope in scopes) {
            let d = scopes[scope];

            if (d.disabled) {
                continue;
            }

            if (!d.object) {
                continue;
            }

            scopeList.push(scope);
        }

        return scopeList;
    }

    /**
     * Get an entity-scope list. Scopes that represents entities.
     *
     * @returns {string[]}
     */
    getScopeEntityList () {
        let scopes = this.get('scopes') || {};
        let scopeList = [];

        for (let scope in scopes) {
            let d = scopes[scope];

            if (d.disabled) {
                continue;
            }

            if (!d.entity) {
                continue;
            }

            scopeList.push(scope);
        }

        return scopeList;
    }
}

Object.assign(Metadata.prototype, Events);

export default Metadata;
PK]<	���view-record-helper.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module view-record-helper */

import {Events} from 'bullbone';

/**
 * @mixes Bull.Events
 */
class ViewRecordHelper {

    /**
     * @param {Object.<string, *>} [defaultFieldStates] Default field states.
     * @param {Object.<string, *>} [defaultPanelStates] Default panel states.
     */
    constructor(defaultFieldStates, defaultPanelStates) {

        /**
         * @private
         * @type {Object}
         */
        this.defaultFieldStates = defaultFieldStates || {};
        /**
         * @private
         * @type {Object}
         */
        this.defaultPanelStates = defaultPanelStates || {};
        /** @private */
        this.fieldStateMap = {};
        /** @private */
        this.panelStateMap = {};
        /** @private */
        this.hiddenFields = {};
        /** @private */
        this.hiddenPanels = {};
        /** @private */
        this.fieldOptionListMap = {};
    }

    /**
     * Get hidden fields.
     *
     * @returns {Object.<string, boolean>}
     */
    getHiddenFields() {
        return this.hiddenFields;
    }

    /**
     * Get hidden panels.
     *
     * @returns {Object.<string,boolean>}
     */
    getHiddenPanels() {
        return this.hiddenPanels;
    }

    /**
     * Set a field-state parameter.
     *
     * @param {string} field A field name.
     * @param {string} name A parameter.
     * @param {*} value A value.
     */
    setFieldStateParam(field, name, value) {
        switch (name) {
            case 'hidden':
                if (value) {
                    this.hiddenFields[field] = true;
                }
                else {
                    delete this.hiddenFields[field];
                }

                break;
        }

        this.fieldStateMap[field] = this.fieldStateMap[field] || {};
        this.fieldStateMap[field][name] = value;

        this.trigger('field-change');
    }

    /**
     * Get a field-state parameter.
     *
     * @param {string} field A field name.
     * @param {string} name A parameter.
     * @returns {*} A value.
     */
    getFieldStateParam(field, name) {
        if (field in this.fieldStateMap) {
            if (name in this.fieldStateMap[field]) {
                return this.fieldStateMap[field][name];
            }
        }

        if (name in this.defaultFieldStates) {
            return this.defaultFieldStates[name];
        }

        return null;
    }

    /**
     * Set a panel-state parameter.
     *
     * @param {string} panel A panel name.
     * @param {string} name A parameter.
     * @param {*} value A value.
     */
    setPanelStateParam(panel, name, value) {
        switch (name) {
            case 'hidden':
                if (value) {
                    this.hiddenPanels[panel] = true;
                } else {
                    delete this.hiddenPanels[panel];
                }
                break;
        }

        this.panelStateMap[panel] = this.panelStateMap[panel] || {};
        this.panelStateMap[panel][name] = value;
    }

    /**
     * Get a panel-state parameter.
     *
     * @param {string} panel A panel name.
     * @param {string} name A parameter.
     * @returns {*} A value.
     */
    getPanelStateParam(panel, name) {
        if (panel in this.panelStateMap) {
            if (name in this.panelStateMap[panel]) {
                return this.panelStateMap[panel][name];
            }
        }

        if (name in this.defaultPanelStates) {
            return this.defaultPanelStates[name];
        }

        return null;
    }

    /**
     * Set a field option list.
     *
     * @param {string} field A field name.
     * @param {string[]} list An option list.
     */
    setFieldOptionList(field, list) {
        this.fieldOptionListMap[field] = list;
    }

    /**
     * Clear a field option list.
     *
     * @param {string} field A field name.
     */
    clearFieldOptionList(field) {
        delete this.fieldOptionListMap[field];
    }

    /**
     * Get a field option list.
     *
     * @param {string} field A field name.
     * @returns {string|null} Null if not set.
     */
    getFieldOptionList(field) {
        return this.fieldOptionListMap[field] || null;
    }

    /**
     * Whether a field option list is set.
     *
     * @param {string} field A field name.
     * @returns {boolean}
     */
    hasFieldOptionList(field) {
        return (field in this.fieldOptionListMap);
    }
}

Object.assign(ViewRecordHelper.prototype, Events);

export default ViewRecordHelper;
PK]w��zaction-handler.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module action-handler */

import {View as BullView} from 'bullbone';

/**
 * An action handler. To be extended by specific action handlers.
 */
class ActionHandler {

    /**
     * @param {module:view} view A view.
     */
    constructor(view) {
        /**
         * @protected
         */
        this.view = view;
    }
}

ActionHandler.extend = BullView.extend;

export default ActionHandler;
PK]Zg�{ { language.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module language */

import {Events} from 'bullbone';

/**
 * A language.
 *
 * @mixes Bull.Events
 */
class Language {

    /** @private */
    url = 'I18n'

    /**
     * @class
     * @param {module:cache} [cache] A cache.
     */
    constructor(cache) {
        /**
         * @private
         * @type {module:cache|null}
         */
        this.cache = cache || null;

        /**
         * @private
         * @type {Object}
         */
        this.data = {};

        /**
         * A name.
         *
         * @type {string}
         */
        this.name = 'default';
    }

    /**
     * Whether an item is set in language data.
     *
     * @param {string} scope A scope.
     * @param {string} category A category.
     * @param {string} name An item name.
     * @returns {boolean}
     */
    has(name, category, scope) {
        if (scope in this.data) {
            if (category in this.data[scope]) {
                if (name in this.data[scope][category]) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Get a value set in language data.
     *
     * @param {string} scope A scope.
     * @param {string} category A category.
     * @param {string} name An item name.
     * @returns {*}
     */
    get(scope, category, name) {
        if (scope in this.data) {
            if (category in this.data[scope]) {
                if (name in this.data[scope][category]) {
                    return this.data[scope][category][name];
                }
            }
        }

        if (scope === 'Global') {
            return name;
        }

        return false;
    }

    /**
     * Translate a label.
     *
     * @param {string} name An item name.
     * @param {string|null} [category='labels'] A category.
     * @param {string|null} [scope='Global'] A scope.
     * @returns {string}
     */
    translate(name, category, scope) {
        scope = scope || 'Global';
        category = category || 'labels';

        let res = this.get(scope, category, name);

        if (res === false && scope !== 'Global') {
            res = this.get('Global', category, name);
        }

        return res;
    }

    /**
     * Translation an option item value.
     *
     * @param {string} value An option value.
     * @param {string} field A field name.
     * @param {string} [scope='Global'] A scope.
     * @returns {string}
     */
    translateOption(value, field, scope) {
        let translation = this.translate(field, 'options', scope);

        if (typeof translation !== 'object') {
            translation = {};
        }

        return translation[value] || value;
    }

    /**
     * @private
     */
    loadFromCache(loadDefault) {
        let name = this.name;

        if (loadDefault) {
            name = 'default';
        }

        if (this.cache) {
            let cached = this.cache.get('app', 'language-' + name);

            if (cached) {
                this.data = cached;

                return true;
            }
        }

        return null;
    }

    /**
     * Clear a language cache.
     */
    clearCache() {
        if (this.cache) {
            this.cache.clear('app', 'language-' + this.name);
        }
    }

    /**
     * @private
     */
    storeToCache(loadDefault) {
        let name = this.name;

        if (loadDefault) {
            name = 'default';
        }

        if (this.cache) {
            this.cache.set('app', 'language-' + name, this.data);
        }
    }

    /**
     * Load data from cache or backend (if not yet cached).
     *
     * @param {Function} [callback] Deprecated.
     * @param {boolean} [disableCache=false] Deprecated
     * @param {boolean} [loadDefault=false] Deprecated.
     * @returns {Promise}
     */
    load(callback, disableCache, loadDefault) {
        if (callback) {
            this.once('sync', callback);
        }

        if (!disableCache) {
            if (this.loadFromCache(loadDefault)) {
                this.trigger('sync');

                return new Promise(resolve => resolve());
            }
        }

        return new Promise(resolve => {
            this.fetch(loadDefault)
                .then(() => resolve());
        });
    }

    /**
     * Load default-language data from the backend.
     *
     * @returns {Promise}
     */
    loadDefault() {
        return this.load(null, false, true);
    }

    /**
     * Load data from the backend.
     *
     * @returns {Promise}
     */
    loadSkipCache() {
        return this.load(null, true);
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * Load default-language data from the backend.
     *
     * @returns {Promise}
     */
    loadDefaultSkipCache() {
        return this.load(null, true, true);
    }

    /**
     * @private
     * @param {boolean} loadDefault
     * @returns {Promise}
     */
    fetch(loadDefault) {
        return Espo.Ajax.getRequest(this.url, {default: loadDefault}).then(data => {
            this.data = data;

            this.storeToCache(loadDefault);
            this.trigger('sync');
        });
    }

    /**
     * Sort a field list by a translated name.
     *
     * @param {string} scope An entity type.
     * @param {string[]} fieldList A field list.
     * @returns {string[]}
     */
    sortFieldList(scope, fieldList) {
        return fieldList.sort((v1, v2) => {
            return this.translate(v1, 'fields', scope)
                .localeCompare(this.translate(v2, 'fields', scope));
        });
    }

    /**
     * Sort an entity type list by a translated name.
     *
     * @param {string[]} entityList An entity type list.
     * @param {boolean} [plural=false] Use a plural label.
     * @returns {string[]}
     */
    sortEntityList(entityList, plural) {
        let category = 'scopeNames';

        if (plural) {
            category += 'Plural';
        }

        return entityList.sort((v1, v2) => {
            return this.translate(v1, category)
                .localeCompare(this.translate(v2, category));
        });
    }

    /**
     * Get a value by a path.
     *
     * @param {string[]|string} path A path.
     * @returns {*}
     */
    translatePath(path) {
        if (typeof path === 'string' || path instanceof String) {
            path = path.split('.');
        }

        let pointer = this.data;

        path.forEach(key => {
            if (key in pointer) {
                pointer = pointer[key];
            }
        });

        return pointer;
    }
}

Object.assign(Language.prototype, Events);

export default Language;
PK]Z�vnumber-util.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module num-util */

/**
 * A number util.
 */
class NumberUtil {

    /**
     * @param {module:models/settings} config A config.
     * @param {module:models/preferences} preferences Preferences.
     */
    constructor(config, preferences) {
        /**
         * @private
         * @type {module:models/settings}
         */
        this.config = config;

        /**
         * @private
         * @type {module:models/preferences}
         */
        this.preferences = preferences;

        /**
         * A thousand separator.
         *
         * @private
         * @type {string|null}
         */
        this.thousandSeparator = null;

        /**
         * A decimal mark.
         *
         * @private
         * @type {string|null}
         */
        this.decimalMark = null;

        this.config.on('change', () => {
            this.thousandSeparator = null;
            this.decimalMark = null;
        });

        this.preferences.on('change', () => {
            this.thousandSeparator = null;
            this.decimalMark = null;
        });

        /**
         * A max decimal places.
         *
         * @private
         * @type {number}
         */
        this.maxDecimalPlaces = 10;
    }

    /**
     * Format an integer number.
     *
     * @param {number} value A value.
     * @returns {string}
     */
    formatInt(value) {
        if (value === null || value === undefined) {
            return '';
        }

        let stringValue = value.toString();

        stringValue = stringValue.replace(/\B(?=(\d{3})+(?!\d))/g, this.getThousandSeparator());

        return stringValue;
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * Format a float number.
     *
     * @param {number} value A value.
     * @param {number} [decimalPlaces] Decimal places.
     * @returns {string}
     */
    formatFloat(value, decimalPlaces) {
        if (value === null || value === undefined) {
            return '';
        }

        if (decimalPlaces === 0) {
            value = Math.round(value);
        }
        else if (decimalPlaces) {
            value = Math.round(value * Math.pow(10, decimalPlaces)) / (Math.pow(10, decimalPlaces));
        }
        else {
            value = Math.round(
                value * Math.pow(10, this.maxDecimalPlaces)) / (Math.pow(10, this.maxDecimalPlaces)
            );
        }

        let parts = value.toString().split('.');

        parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, this.getThousandSeparator());

        if (decimalPlaces === 0) {
            return parts[0];
        }

        if (decimalPlaces) {
            let decimalPartLength = 0;

            if (parts.length > 1) {
                decimalPartLength = parts[1].length;
            }
            else {
                parts[1] = '';
            }

            if (decimalPlaces && decimalPartLength < decimalPlaces) {
                let limit = decimalPlaces - decimalPartLength;

                for (let i = 0; i < limit; i++) {
                    parts[1] += '0';
                }
            }
        }

        return parts.join(this.getDecimalMark());
    }

    /**
     * @private
     * @returns {string}
     */
    getThousandSeparator() {
        if (this.thousandSeparator !== null) {
            return this.thousandSeparator;
        }

        let thousandSeparator = '.';

        if (this.preferences.has('thousandSeparator')) {
            thousandSeparator = this.preferences.get('thousandSeparator');
        }
        else if (this.config.has('thousandSeparator')) {
            thousandSeparator = this.config.get('thousandSeparator');
        }

        /**
         * A thousand separator.
         *
         * @private
         * @type {string|null}
         */
        this.thousandSeparator = thousandSeparator;

        return thousandSeparator;
    }

    /**
     * @private
     * @returns {string}
     */
    getDecimalMark() {
        if (this.decimalMark !== null) {
            return this.decimalMark;
        }

        let decimalMark = '.';

        if (this.preferences.has('decimalMark')) {
            decimalMark = this.preferences.get('decimalMark');
        }
        else {
            if (this.config.has('decimalMark')) {
                decimalMark = this.config.get('decimalMark');
            }
        }

        /**
         * A decimal mark.
         *
         * @private
         * @type {string|null}
         */
        this.decimalMark = decimalMark;

        return decimalMark;
    }
}

export default NumberUtil;
PK]{gɟzzacl-portal/email.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import AclPortal from 'acl-portal';

class EmailAclPortal extends AclPortal {

    // noinspection JSUnusedGlobalSymbols
    checkModelRead(model, data, precise) {
        let result = this.checkModel(model, data, 'read', precise);

        if (result) {
            return true;
        }

        if (data === false) {
            return false;
        }

        let d = data || {};

        if (d.read === 'no') {
            return false;
        }

        if (model.has('usersIds')) {
            if (~(model.get('usersIds') || []).indexOf(this.getUser().id)) {
                return true;
            }
        }
        else if (precise) {
            return null;
        }

        return result;
    }
}

export default EmailAclPortal;
PK]H�h_��acl-portal/notification.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import AclPortal from 'acl-portal';

class NotificationAclPortal extends AclPortal {

    checkIsOwner(model) {
        if (this.getUser().id === model.get('userId')) {
            return true;
        }

        return false;
    }
}

export default NotificationAclPortal;
PK]'M�g||acl-portal/preferences.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import AclPortal from 'acl-portal';

class PreferencesAclPortal extends AclPortal {

    checkIsOwner(model) {
        if (this.getUser().id === model.id) {
            return true;
        }

        return false;
    }
}

export default PreferencesAclPortal;
PK]1�O��
storage.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module storage */

/**
 * A storage. Data is saved across browser sessions, has no expiration time.
 */
class Storage {

    constructor() {}

    /** @protected */
    prefix = 'espo'

    /** @protected */
    storageObject = localStorage

    /**
     * @private
     * @param {string} type
     * @returns {string}
     */
    composeFullPrefix(type) {
        return this.prefix + '-' + type;
    }

    /**
     * @private
     * @param {string} type
     * @param {string} name
     * @returns {string}
     */
    composeKey(type, name) {
        return this.composeFullPrefix(type) + '-' + name;
    }

    /**
     * @private
     * @param {string} type
     */
    checkType(type) {
        if (
            typeof type === 'undefined' &&
            toString.call(type) !== '[object String]' || type === 'cache'
        ) {
            throw new TypeError("Bad type \"" + type + "\" passed to Espo.Storage.");
        }
    }

    /**
     * Has a value.
     *
     * @param {string} type A type (category).
     * @param {string} name A name.
     * @returns {boolean}
     */
    has(type, name) {
        this.checkType(type);

        let key = this.composeKey(type, name);

        return this.storageObject.getItem(key) !== null;
    }

    /**
     * Get a value.
     *
     * @param {string} type A type (category).
     * @param {string} name A name.
     * @returns {*} Null if not stored.
     */
    get(type, name) {
        this.checkType(type);

        let key = this.composeKey(type, name);

        try {
            var stored = this.storageObject.getItem(key);
        }
        catch (error) {
            console.error(error);

            return null;
        }

        if (stored) {
            let result = stored;

            if (stored.length > 9 && stored.substring(0, 9) === '__JSON__:') {
                let jsonString = stored.slice(9);

                try {
                    result = JSON.parse(jsonString);
                }
                catch (error) {
                    result = stored;
                }
            }
            else if (stored[0] === "{" || stored[0] === "[") { // for backward compatibility
                try {
                    result = JSON.parse(stored);
                }
                catch (error) {
                    result = stored;
                }
            }

            return result;
        }

        return null;
    }

    /**
     * Set (store) a value.
     *
     * @param {string} type A type (category).
     * @param {string} name A name.
     * @param {*} value A value.
     */
    set(type, name, value) {
        this.checkType(type);

        if (value === null) {
            this.clear(type, name);

            return;
        }

        let key = this.composeKey(type, name);

        if (
            value instanceof Object ||
            Array.isArray(value) ||
            value === true ||
            value === false ||
            typeof value === 'number'
        ) {
            value = '__JSON__:' + JSON.stringify(value);
        }

        try {
            this.storageObject.setItem(key, value);
        }
        catch (error) {
            console.error(error);

            return null;
        }
    }

    /**
     * Clear a value.
     *
     * @param {string} type A type (category).
     * @param {string} name A name.
     */
    clear(type, name) {
        let reText;

        if (typeof type !== 'undefined') {
            if (typeof name === 'undefined') {
                reText = '^' + this.composeFullPrefix(type);
            }
            else {
                reText = '^' + this.composeKey(type, name);
            }
        }
        else {
            reText = '^' + this.prefix + '-';
        }

        let re = new RegExp(reText);

        for (let i in this.storageObject) {
            if (re.test(i)) {
                delete this.storageObject[i];
            }
        }
    }
}

export default Storage;
PK]X��m>>
app-portal.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module app-portal */

import App from 'app';
import AclPortalManager from 'acl-portal-manager';

/**
 * A portal application class.
 */
class AppPortal extends App {

    aclName = 'aclPortal'
    masterView = 'views/site-portal/master'

    createAclManager() {
        return new AclPortalManager(
            this.user,
            null,
            this.settings.get('aclAllowDeleteCreated')
        );
    }
}

export default AppPortal
PK]��4כ
�
multi-collection.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module multi-collection */

import Collection from 'collection';

/**
 * A collection that can contain entities of different entity types.
 */
class MultiCollection extends Collection {

    /**
     * A model seed map.
     *
     * @public
     * @type {Object.<string, module:model>}
     */
    seeds = null

    /** @inheritDoc */
    prepareAttributes(response, options) {
        this.total = response.total;

        if (!('list' in response)) {
            throw new Error("No 'list' in response.");
        }

        /** @type {({_scope?: string} & Object.<string, *>)[]} */
        const list = response.list;

        return list.map(attributes => {
            let entityType = attributes._scope;

            if (!entityType) {
                throw new Error("No '_scope' attribute.");
            }

            attributes = _.clone(attributes);
            delete attributes['_scope'];

            let model = this.seeds[entityType].clone();

            model.set(attributes);

            return model;
        });
    }

    /** @inheritDoc */
    clone() {
        let collection = super.clone();

        collection.seeds = this.seeds;

        return collection;
    }
}

// noinspection JSUnusedGlobalSymbols
export default MultiCollection;
PK]{�8�
�
session-storage.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module session-storage */

/**
 * A session storage. Cleared when a page session ends.
 */
class SessionStorage {

    /** @private */
    storageObject = sessionStorage

    /**
     * Get a value.
     *
     * @param {string} name A name.
     * @returns {*} Null if not set.
     */
    get(name) {
        try {
            var stored = this.storageObject.getItem(name);
        }
        catch (error) {
            console.error(error);

            return null;
        }

        if (stored) {
            let result = stored;

            if (stored.length > 9 && stored.substring(0, 9) === '__JSON__:') {
                let jsonString = stored.slice(9);

                try {
                    result = JSON.parse(jsonString);
                }
                catch (error) {
                    result = stored;
                }
            }

            return result;
        }

        return null;
    }

    /**
     * Set (store) a value.
     *
     * @param {string} name A name.
     * @param {*} value A value.
     */
    set(name, value) {
        if (value === null) {
            this.clear(name);

            return;
        }

        if (
            value instanceof Object ||
            Array.isArray(value) ||
            value === true ||
            value === false ||
            typeof value === 'number'
        ) {
            value = '__JSON__:' + JSON.stringify(value);
        }

        try {
            this.storageObject.setItem(name, value);
        }
        catch (error) {
            console.error(error);
        }
    }

    /**
     * Has a value.
     *
     * @param {string} name A name.
     * @returns {boolean}
     */
    has(name) {
        return this.storageObject.getItem(name) !== null;
    }

    /**
     * Clear a value.
     *
     * @param {string} name A name.
     */
    clear(name) {
        for (let i in this.storageObject) {
            if (i === name) {
                delete this.storageObject[i];
            }
        }
    }
}

export default SessionStorage;
PK]�88acl-manager.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module acl-manager */

import Acl from 'acl';
import Utils from 'utils';
import {View as BullView} from 'bullbone';

/**
 * An action.
 *
 * @typedef {'create'|'read'|'edit'|'delete'|'stream'} module:acl-manager~action
 */

/**
 * An access checking class for a specific scope.
 */
class AclManager {

    /** @protected */
    data = null
    fieldLevelList = ['yes', 'no']

    /**
     * @param {module:models/user} user A user.
     * @param {Object} implementationClassMap `acl` implementations.
     * @param {boolean} aclAllowDeleteCreated Allow a user to delete records they created regardless a
     *   role access level.
     */
    constructor(user, implementationClassMap, aclAllowDeleteCreated) {
        this.setEmpty();

        /** @protected */
        this.user = user || null;
        this.implementationClassMap = implementationClassMap || {};
        this.aclAllowDeleteCreated = aclAllowDeleteCreated;
    }

    /**
     * @protected
     */
    setEmpty() {
        this.data = {
            table: {},
            fieldTable:  {},
            fieldTableQuickAccess: {},
        };

        this.implementationHash = {};
        this.forbiddenFieldsCache = {};
        this.implementationClassMap = {};
        this.forbiddenAttributesCache = {};
    }

    /**
     * Get an `acl` implementation.
     *
     * @protected
     * @param {string} scope A scope.
     * @returns {module:acl}
     */
    getImplementation(scope) {
        if (!(scope in this.implementationHash)) {
            let implementationClass = Acl;

            if (scope in this.implementationClassMap) {
                implementationClass = this.implementationClassMap[scope];
            }

            let forbiddenFieldList = this.getScopeForbiddenFieldList(scope);

            let params = {
                aclAllowDeleteCreated: this.aclAllowDeleteCreated,
                teamsFieldIsForbidden: !!~forbiddenFieldList.indexOf('teams'),
                forbiddenFieldList: forbiddenFieldList,
            };

            this.implementationHash[scope] = new implementationClass(this.getUser(), scope, params);
        }

        return this.implementationHash[scope];
    }

    /**
     * @protected
     */
    getUser() {
        return this.user;
    }

    /**
     * @internal
     */
    set(data) {
        data = data || {};

        this.data = data;
        this.data.table = this.data.table || {};
        this.data.fieldTable = this.data.fieldTable || {};
        this.data.attributeTable = this.data.attributeTable || {};
    }

    /**
     * @deprecated Use `getPermissionLevel`.
     *
     * @returns {string|null}
     */
    get(name) {
        return this.data[name] || null;
    }

    /**
     * Get a permission level.
     *
     * @param {string} permission A permission name.
     * @returns {'yes'|'all'|'team'|'no'}
     */
    getPermissionLevel(permission) {
        let permissionKey = permission;

        if (permission.slice(-10) !== 'Permission') {
            permissionKey = permission + 'Permission';
        }

        return this.data[permissionKey] || 'no';
    }

    /**
     * Get access level to a scope action.
     *
     * @param {string} scope A scope.
     * @param {module:acl-manager~action} action An action.
     * @returns {'yes'|'all'|'team'|'no'|null}
     */
    getLevel(scope, action) {
        if (!(scope in this.data.table)) {
            return null;
        }

        if (typeof this.data.table[scope] !== 'object' || !(action in this.data.table[scope])) {
            return null;
        }

        return this.data.table[scope][action];
    }

    /**
     * Clear access data.
     *
     * @internal
     */
    clear() {
        this.setEmpty();
    }

    /**
     * Check whether a scope has ACL.
     *
     * @param {string} scope A scope.
     * @returns {boolean}
     */
    checkScopeHasAcl(scope) {
        var data = (this.data.table || {})[scope];

        if (typeof data === 'undefined') {
            return false;
        }

        return true;
    }

    /**
     * Check access to a scope.
     *
     * @param {string} scope A scope.
     * @param {module:acl-manager~action|null} [action=null] An action.
     * @param {boolean} [precise=false] Deprecated. Not used.
     * @returns {boolean} True if access allowed.
     */
    checkScope(scope, action, precise) {
        let data = (this.data.table || {})[scope];

        if (typeof data === 'undefined') {
            data = null;
        }

        return this.getImplementation(scope).checkScope(data, action, precise);
    }

    /**
     * Check access to a model.
     *
     * @param {module:model} model A model.
     * @param {module:acl-manager~action|null} [action=null] An action.
     * @param {boolean} [precise=false] To return `null` if not enough data is set in a model.
     *   E.g. the `teams` field is not yet loaded.
     * @returns {boolean|null} True if access allowed, null if not enough data to determine.
     */
    checkModel(model, action, precise) {
        let scope = model.entityType;

        // todo move this to custom acl
        if (action === 'edit') {
            if (!model.isEditable()) {
                return false;
            }
        }

        if (action === 'delete') {
            if (!model.isRemovable()) {
                return false;
            }
        }

        let data = (this.data.table || {})[scope];

        if (typeof data === 'undefined') {
            data = null;
        }

        let impl = this.getImplementation(scope);

        if (action) {
            let methodName = 'checkModel' + Utils.upperCaseFirst(action);

            if (methodName in impl) {
                return impl[methodName](model, data, precise);
            }
        }

        return impl.checkModel(model, data, action, precise);
    }

    /**
     * Check access to a scope or a model.
     *
     * @param {string|module:model} subject What to check. A scope or a model.
     * @param {module:acl-manager~action|null} [action=null] An action.
     * @param {boolean} [precise=false]  To return `null` if not enough data is set in a model.
     *   E.g. the `teams` field is not yet loaded.
     * @returns {boolean|null} True if access allowed, null if not enough data to determine.
     */
    check(subject, action, precise) {
        if (typeof subject === 'string') {
            return this.checkScope(subject, action, precise);
        }

        return this.checkModel(subject, action, precise);
    }

    /**
     * Check if a user is owner to a model.
     *
     * @param {module:model} model A model.
     * @returns {boolean|null} True if owner, null if not clear.
     */
    checkIsOwner(model) {
        return this.getImplementation(model.entityType).checkIsOwner(model);
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * Check if a user in a team of a model.
     *
     * @param {module:model} model A model.
     * @returns {boolean|null} True if in a team, null if not clear.
     */
    checkInTeam(model) {
        return this.getImplementation(model.entityType).checkInTeam(model);
    }

    /**
     * Check an assignment permission to a user.
     *
     * @param {module:models/user} user A user.
     * @returns {boolean} True if access allowed.
     */
    checkAssignmentPermission(user) {
        return this.checkPermission('assignmentPermission', user);
    }

    /**
     * Check a user permission to a user.
     *
     * @param {module:models/user} user A user.
     * @returns {boolean} True if access allowed.
     */
    checkUserPermission(user) {
        return this.checkPermission('userPermission', user);
    }

    /**
     * Check a specific permission to a user.
     *
     * @param {string} permission A permission name.
     * @param {module:models/user} user A user.
     * @returns {boolean} True if access allowed.
     */
    checkPermission(permission, user) {
        if (this.getUser().isAdmin()) {
            return true;
        }

        let level = this.getPermissionLevel(permission);

        if (level === 'no') {
            if (user.id === this.getUser().id) {
                return true;
            }

            return false;
        }

        if (level === 'team') {
            if (!user.has('teamsIds')) {
                return false;
            }

            let result = false;

            let teamsIds = user.get('teamsIds') || [];

            teamsIds.forEach(id => {
                if (~(this.getUser().get('teamsIds') || []).indexOf(id)) {
                    result = true;
                }
            });

            return result;
        }

        if (level === 'all') {
            return true;
        }

        if (level === 'yes') {
            return true;
        }

        return false;
    }

    /**
     * Get a list of forbidden fields for an entity type.
     *
     * @param {string} scope An entity type.
     * @param {'read'|'edit'} [action='read'] An action.
     * @param {'yes'|'no'} [thresholdLevel='no'] A threshold level.
     * @returns {string[]} A forbidden field list.
     */
    getScopeForbiddenFieldList(scope, action, thresholdLevel) {
        action = action || 'read';
        thresholdLevel = thresholdLevel || 'no';

        let key = scope + '_' + action + '_' + thresholdLevel;

        if (key in this.forbiddenFieldsCache) {
            return Utils.clone(this.forbiddenFieldsCache[key]);
        }

        let levelList = this.fieldLevelList.slice(this.fieldLevelList.indexOf(thresholdLevel));

        let fieldTableQuickAccess = this.data.fieldTableQuickAccess || {};
        let scopeData = fieldTableQuickAccess[scope] || {};
        let fieldsData = scopeData.fields || {};
        let actionData = fieldsData[action] || {};

        let fieldList = [];

        levelList.forEach(level => {
            let list = actionData[level] || [];

            list.forEach(field => {
                if (~fieldList.indexOf(field)) {
                    return;
                }

                fieldList.push(field);
            });
        });

        this.forbiddenFieldsCache[key] = fieldList;

        return Utils.clone(fieldList);
    }

    /**
     * Get a list of forbidden attributes for an entity type.
     *
     * @param {string} scope An entity type.
     * @param {'read'|'edit'} [action='read'] An action.
     * @param {'yes'|'no'} [thresholdLevel='no'] A threshold level.
     * @returns {string[]} A forbidden attribute list.
     */
    getScopeForbiddenAttributeList(scope, action, thresholdLevel) {
        action = action || 'read';
        thresholdLevel = thresholdLevel || 'no';

        let key = scope + '_' + action + '_' + thresholdLevel;

        if (key in this.forbiddenAttributesCache) {
            return Utils.clone(this.forbiddenAttributesCache[key]);
        }

        let levelList = this.fieldLevelList.slice(this.fieldLevelList.indexOf(thresholdLevel));

        let fieldTableQuickAccess = this.data.fieldTableQuickAccess || {};
        let scopeData = fieldTableQuickAccess[scope] || {};

        let attributesData = scopeData.attributes || {};
        let actionData = attributesData[action] || {};

        let attributeList = [];

        levelList.forEach(level => {
            let list = actionData[level] || [];

            list.forEach(attribute => {
                if (~attributeList.indexOf(attribute)) {
                    return;
                }

                attributeList.push(attribute);
            });
        });

        this.forbiddenAttributesCache[key] = attributeList;

        return Utils.clone(attributeList);
    }

    /**
     * Check an assignment permission to a team.
     *
     * @param {string} teamId A team ID.
     * @returns {boolean} True if access allowed.
     */
    checkTeamAssignmentPermission(teamId) {
        if (this.getPermissionLevel('assignmentPermission') === 'all') {
            return true;
        }

        return !!~this.getUser().getLinkMultipleIdList('teams').indexOf(teamId);
    }

    /**
     * Check access to a field.
     * @param {string} scope An entity type.
     * @param {string} field A field.
     * @param {'read'|'edit'} [action='read'] An action.
     * @returns {boolean} True if access allowed.
     */
    checkField(scope, field, action) {
        return !~this.getScopeForbiddenFieldList(scope, action).indexOf(field);
    }
}

AclManager.extend = BullView.extend;

export default AclManager;
PK] q	��helpers/action-item-setup.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module helpers/action-item-setup */

class ActionItemSetupHelper {
    /**
     * @param {module:metadata} metadata
     * @param {module:view-helper} viewHelper
     * @param {module:acl-manager} acl
     * @param {module:language} language
     */
    constructor(metadata, viewHelper, acl, language) {
        this.metadata = metadata;
        this.viewHelper = viewHelper;
        this.acl = acl;
        this.language = language;
    }

    /**
     * @param {module:view} view
     * @param {string} type
     * @param {function(Promise): void} waitFunc
     * @param {function(Object): void} addFunc
     * @param {function(string): void} showFunc
     * @param {function(string): void} hideFunc
     * @param {{listenToViewModelSync?: boolean}} [options]
     */
    setup(view, type, waitFunc, addFunc, showFunc, hideFunc, options) {
        options = options || {};
        let actionList = [];

        let scope = view.scope || view.model.entityType;

        if (!scope) {
            throw new Error();
        }

        let actionDefsList = [
            ...this.metadata.get(['clientDefs', 'Global', type + 'ActionList']) || [],
            ...this.metadata.get(['clientDefs', scope, type + 'ActionList']) || [],
        ];

        actionDefsList.forEach(item => {
            if (typeof item === 'string') {
                item = {name: item};
            }

            item = Espo.Utils.cloneDeep(item);

            let name = item.name;

            if (!item.label) {
                item.html = this.language.translate(name, 'actions', scope);
            }

            item.data = item.data || {};

            let handlerName = item.handler || item.data.handler;

            if (handlerName && !item.data.handler) {
                item.data.handler = handlerName;
            }

            addFunc(item);

            if (!Espo.Utils.checkActionAvailability(this.viewHelper, item)) {
                return;
            }

            if (!Espo.Utils.checkActionAccess(this.acl, view.model, item, true)) {
                item.hidden = true;
            }

            actionList.push(item);

            if (!handlerName) {
                return;
            }

            if (!item.initFunction && !item.checkVisibilityFunction) {
                return;
            }

            waitFunc(new Promise(resolve => {
                Espo.loader.require(handlerName, Handler => {
                    let handler = new Handler(view);

                    if (item.initFunction) {
                        handler[item.initFunction].call(handler);
                    }

                    if (item.checkVisibilityFunction) {
                        let isNotVisible = !handler[item.checkVisibilityFunction].call(handler);

                        if (isNotVisible) {
                            hideFunc(item.name);
                        }
                    }

                    item.handlerInstance = handler;

                    resolve();
                });
            }));
        });

        if (!actionList.length) {
            return;
        }

        let onSync = () => {
            actionList.forEach(item => {
                if (item.handlerInstance && item.checkVisibilityFunction) {
                    let isNotVisible = !item.handlerInstance[item.checkVisibilityFunction]
                        .call(item.handlerInstance);

                    if (isNotVisible) {
                        hideFunc(item.name);

                        return;
                    }
                }

                if (Espo.Utils.checkActionAccess(this.acl, view.model, item, true)) {
                    showFunc(item.name);

                    return;
                }

                hideFunc(item.name);
            });
        };

        if (options.listenToViewModelSync) {
            view.listenTo(view, 'model-sync', () => onSync());

            return;
        }

        view.listenTo(view.model, 'sync', () => onSync());
    }
}

export default ActionItemSetupHelper;
PK]+**���helpers/misc/field-language.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module helpers/misc/field-language */

/**
 * A field-language util.
 */
class FieldLanguage {

    /**
     * @param {module:metadata} metadata A metadata.
     * @param {module:language} language A language.
     */
    constructor(metadata, language) {
        /**
         * @private
         * @type {module:metadata}
         */
        this.metadata = metadata;

        /**
         * @private
         * @type {module:language}
         */
        this.language = language;
    }

    /**
     * Translate an attribute.
     *
     * @param {string} scope A scope.
     * @param {string} name An attribute name.
     * @returns {string}
     */
    translateAttribute(scope, name) {
        let label = this.language.translate(name, 'fields', scope);

        if (name.indexOf('Id') === name.length - 2) {
            let baseField = name.slice(0, name.length - 2);

            if (this.metadata.get(['entityDefs', scope, 'fields', baseField])) {
                label = this.language.translate(baseField, 'fields', scope) +
                    ' (' + this.language.translate('id', 'fields') + ')';
            }
        }
        else if (name.indexOf('Name') === name.length - 4) {
            let baseField = name.slice(0, name.length - 4);

            if (this.metadata.get(['entityDefs', scope, 'fields', baseField])) {
                label = this.language.translate(baseField, 'fields', scope) +
                    ' (' + this.language.translate('name', 'fields') + ')';
            }
        }
        else if (name.indexOf('Type') === name.length - 4) {
            let baseField = name.slice(0, name.length - 4);

            if (this.metadata.get(['entityDefs', scope, 'fields', baseField])) {
                label = this.language.translate(baseField, 'fields', scope) +
                    ' (' + this.language.translate('type', 'fields') + ')';
            }
        }

        if (name.indexOf('Ids') === name.length - 3) {
            let baseField = name.slice(0, name.length - 3);

            if (this.metadata.get(['entityDefs', scope, 'fields', baseField])) {
                label = this.language.translate(baseField, 'fields', scope) +
                    ' (' + this.language.translate('ids', 'fields') + ')';
            }
        }
        else if (name.indexOf('Names') === name.length - 5) {
            let baseField = name.slice(0, name.length - 5);

            if (this.metadata.get(['entityDefs', scope, 'fields', baseField])) {
                label = this.language.translate(baseField, 'fields', scope) +
                    ' (' + this.language.translate('names', 'fields') + ')';
            }
        }
        else if (name.indexOf('Types') === name.length - 5) {
            let baseField = name.slice(0, name.length - 5);

            if (this.metadata.get(['entityDefs', scope, 'fields', baseField])) {
                label = this.language.translate(baseField, 'fields', scope) +
                    ' (' + this.language.translate('types', 'fields') + ')';
            }
        }

        return label;
    }
}

export default FieldLanguage;
PK]uI} ��"helpers/misc/stored-text-search.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module helpers/misc/stored-text-search */

export default class {
    /**
     * @param {module:storage} storage
     * @param {string} scope
     * @param {Number} [maxCount]
     */
    constructor(scope, storage, maxCount) {
        this.scope = scope;
        this.storage = storage;
        this.key = 'textSearches';
        this.maxCount = maxCount || 100;
        /** @type {string[]|null} */
        this.list = null;
    }

    /**
     * Match.
     *
     * @param {string} text
     * @param {Number} [limit]
     * @return {string[]}
     */
    match(text, limit) {
        text = text.toLowerCase().trim();

        let list = this.get();
        let matchedList = [];

        for (let item of list) {
            if (item.toLowerCase().startsWith(text)) {
                matchedList.push(item);
            }

            if (limit !== undefined && matchedList.length === limit) {
                break;
            }
        }

        return matchedList;
    }

    /**
     * Get stored text filters.
     *
     * @private
     * @return {string[]}
     */
    get() {
        if (this.list === null) {
            this.list = this.getFromStorage();
        }

        return this.list;
    }

    /**
     * @private
     * @return {string[]}
     */
    getFromStorage() {
        /** @var {string[]} */
        return this.storage.get(this.key, this.scope) || [];
    }

    /**
     * Store a text filter.
     *
     * @param {string} text
     */
    store(text) {
        text = text.trim();

        let list = this.getFromStorage();

        let index = list.indexOf(text);

        if (index !== -1) {
            list.splice(index, 1);
        }

        list.unshift(text);

        if (list.length > this.maxCount) {
            list = list.slice(0, this.maxCount);
        }

        this.list = list;
        this.storage.set(this.key, this.scope, list);
    }

    /**
     * Remove a text filter.
     *
     * @param {string} text
     */
    remove(text) {
        text = text.trim();

        let list = this.getFromStorage();

        let index = list.indexOf(text);

        if (index === -1) {
            return;
        }

        list.splice(index, 1);

        this.list = list;
        this.storage.set(this.key, this.scope, list);
    }
}
PK]\wfmm'helpers/misc/authentication-provider.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module module:helpers/misc/authentication-provider */

export default class {
    /**
     * @param {module:views/record/detail} view A view.
     */
    constructor(view) {
        /**
         * @private
         * @type {module:views/record/detail}
         */
        this.view = view;

        this.metadata = view.getMetadata();

        /**
         * @private
         * @type {module:model}
         */
        this.model = view.model;

        /** @var {Object.<string, Object.<string, *>>} defs */
        let defs = view.getMetadata().get(['authenticationMethods']) || {};

        /**
         * @private
         * @type {string[]}
         */
        this.methodList = Object.keys(defs).filter(item => {
            /** @var {Object.<string, *>} */
            let data = defs[item].provider || {};

            return data.isAvailable;
        });

        /** @private */
        this.authFields = {};

        /** @private */
        this.dynamicLogicDefs = {
            fields: {},
            panels: {},
        };
    }

    /**
     * @param {function(): void} callback
     */
    setupPanelsVisibility(callback) {
        this.handlePanelsVisibility(callback);

        this.view.listenTo(this.model, 'change:method', () => this.handlePanelsVisibility(callback));
    }

    /**
     * @private
     * @param {string} method
     * @param {string} param
     * @return {*}
     */
    getFromMetadata(method, param) {
        return this.metadata
            .get(['authenticationMethods', method, 'provider', param]) ||
        this.metadata
            .get(['authenticationMethods', method, 'settings', param]);
    }

    /**
     * @return {Object}
     */
    setupMethods() {
        this.methodList.forEach(method => this.setupMethod(method));

        return this.dynamicLogicDefs;
    }

    /**
     * @private
     */
    setupMethod(method) {
        /** @var {string[]} */
        let fieldList = this.getFromMetadata(method, 'fieldList') || [];

        fieldList = fieldList.filter(item => this.model.hasField(item));

        this.authFields[method] = fieldList;

        let mDynamicLogicFieldsDefs = (this.getFromMetadata(method, 'dynamicLogic') || {}).fields || {};

        for (let f in mDynamicLogicFieldsDefs) {
            if (!fieldList.includes(f)) {
                continue;
            }

            let defs = this.modifyDynamicLogic(mDynamicLogicFieldsDefs[f]);

            this.dynamicLogicDefs.fields[f] = Espo.Utils.cloneDeep(defs);
        }
    }

    /**
     * @private
     */
    modifyDynamicLogic(defs) {
        defs = Espo.Utils.clone(defs);

        if (Array.isArray(defs)) {
            return defs.map(item => this.modifyDynamicLogic(item));
        }

        if (typeof defs === 'object') {
            let o = {};

            for (let property in defs) {
                let value = defs[property];

                if (property === 'attribute' && value === 'authenticationMethod') {
                    value = 'method';
                }

                o[property] = this.modifyDynamicLogic(value);
            }

            return o;
        }

        return defs;
    }

    modifyDetailLayout(layout) {
        this.methodList.forEach(method => {
            let mLayout = this.getFromMetadata(method, 'layout');

            if (!mLayout) {
                return;
            }

            mLayout = Espo.Utils.cloneDeep(mLayout);
            mLayout.name = method;

            this.prepareLayout(mLayout, method);

            layout.push(mLayout);
        });
    }

    prepareLayout(layout, method) {
        layout.rows.forEach(row => {
            row
                .filter(item => !item.noLabel && !item.labelText && item.name)
                .forEach(item => {
                    if (item === null) {
                        return;
                    }

                    let labelText = this.view.translate(item.name, 'fields', 'Settings');

                    item.options = item.options || {};

                    if (labelText && labelText.toLowerCase().indexOf(method.toLowerCase() + ' ') === 0) {
                        item.labelText = labelText.substring(method.length + 1);
                    }

                    item.options.tooltipText = this.view.translate(item.name, 'tooltips', 'Settings');
                });
        });

        layout.rows = layout.rows.map(row => {
            row = row.map(cell => {
                if (
                    cell &&
                    cell.name &&
                    !this.model.hasField(cell.name)
                ) {
                    return false;
                }

                return cell;
            })

            return row;
        });
    }

    /**
     * @private
     * @param {function(): void} callback
     */
    handlePanelsVisibility(callback) {
        let authenticationMethod = this.model.get('method');

        this.methodList.forEach(method => {
            let fieldList = (this.authFields[method] || []);

            if (method !== authenticationMethod) {
                this.view.hidePanel(method);

                fieldList.forEach(field => {
                    this.view.hideField(field);
                });

                return;
            }

            this.view.showPanel(method);

            fieldList.forEach(field => this.view.showField(field));

            callback();
        });
    }
}
PK]uI} ��&helpers/misc/list-select-attributes.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module helpers/misc/stored-text-search */

export default class {
    /**
     * @param {module:storage} storage
     * @param {string} scope
     * @param {Number} [maxCount]
     */
    constructor(scope, storage, maxCount) {
        this.scope = scope;
        this.storage = storage;
        this.key = 'textSearches';
        this.maxCount = maxCount || 100;
        /** @type {string[]|null} */
        this.list = null;
    }

    /**
     * Match.
     *
     * @param {string} text
     * @param {Number} [limit]
     * @return {string[]}
     */
    match(text, limit) {
        text = text.toLowerCase().trim();

        let list = this.get();
        let matchedList = [];

        for (let item of list) {
            if (item.toLowerCase().startsWith(text)) {
                matchedList.push(item);
            }

            if (limit !== undefined && matchedList.length === limit) {
                break;
            }
        }

        return matchedList;
    }

    /**
     * Get stored text filters.
     *
     * @private
     * @return {string[]}
     */
    get() {
        if (this.list === null) {
            this.list = this.getFromStorage();
        }

        return this.list;
    }

    /**
     * @private
     * @return {string[]}
     */
    getFromStorage() {
        /** @var {string[]} */
        return this.storage.get(this.key, this.scope) || [];
    }

    /**
     * Store a text filter.
     *
     * @param {string} text
     */
    store(text) {
        text = text.trim();

        let list = this.getFromStorage();

        let index = list.indexOf(text);

        if (index !== -1) {
            list.splice(index, 1);
        }

        list.unshift(text);

        if (list.length > this.maxCount) {
            list = list.slice(0, this.maxCount);
        }

        this.list = list;
        this.storage.set(this.key, this.scope, list);
    }

    /**
     * Remove a text filter.
     *
     * @param {string} text
     */
    remove(text) {
        text = text.trim();

        let list = this.getFromStorage();

        let index = list.indexOf(text);

        if (index === -1) {
            return;
        }

        list.splice(index, 1);

        this.list = list;
        this.storage.set(this.key, this.scope, list);
    }
}
PK]���J�
�
helpers/misc/foreign-field.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module helpers/misc/foreign-field */

export default class {

    /**
     * @param {module:views/fields/base} view A field view.
     */
    constructor(view) {
        /**
         * @private
         * @type {module:views/fields/base}
         */
        this.view = view;

        let metadata = view.getMetadata();
        let model = view.model;
        let field = view.params.field;
        let link = view.params.link;

        let entityType = metadata.get(['entityDefs', model.entityType, 'links', link, 'entity']) ||
            model.entityType;

        let fieldDefs = metadata.get(['entityDefs', entityType, 'fields', field]) || {};
        let type = fieldDefs.type;

        let ignoreList = [
            'default',
            'audited',
            'readOnly',
            'required',
        ];

        /** @private */
        this.foreignParams = {};

        view.getFieldManager().getParamList(type).forEach(defs => {
            let name = defs.name;

            if (ignoreList.includes(name)) {
                return;
            }

            this.foreignParams[name] = fieldDefs[name] || null;
        });
    }

    /**
     * @return {Object.<string, *>}
     */
    getForeignParams() {
        return Espo.Utils.cloneDeep(this.foreignParams);
    }
}
PK]V���helpers/record-modal.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/**
 * A record-modal helper.
 */
class RecordModalHelper {
    /**
     * @param {module:metadata} metadata
     * @param {module:acl-manager} acl
     */
    constructor(metadata, acl) {
        this.metadata = metadata;
        this.acl = acl;
    }

    /**
     * @param {module:view} view
     * @param {{
     *   id: string,
     *   scope: string,
     *   model?: module:model,
     *   editDisabled?: boolean,
     *   rootUrl?: string,
     * }} params
     * @return {Promise}
     */
    showDetail(view, params) {
        let id = params.id;
        let scope = params.scope;
        let model = params.model;

        if (!id || !scope) {
            console.error("Bad data.");

            return Promise.reject();
        }

        if (model && !this.acl.checkScope(model.entityType, 'read')) {
            return Promise.reject();
        }

        let viewName = this.metadata.get(['clientDefs', scope, 'modalViews', 'detail']) ||
            'views/modals/detail';

        Espo.Ui.notify(' ... ');

        let options = {
            scope: scope,
            model: model,
            id: id,
            quickEditDisabled: params.editDisabled,
            rootUrl: params.rootUrl,
        };

        return view.createView('modal', viewName, options, modalView => {
            modalView.render()
                .then(() => Espo.Ui.notify(false));

            view.listenToOnce(modalView, 'remove', () => {
                view.clearView('modal');
            });
        });
    }
}

export default RecordModalHelper;
PK]�EC�
�
helpers/mass-action.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/**
 * A mass-action helper.
 */
class MassActionHelper {

    /**
     * @param {module:view} view A view.
     */
    constructor(view) {
        /**
         * @private
         * @type {module:view}
         */
        this.view = view;

        /**
         * @private
         * @type {module:models/settings}
         */
        this.config = view.getConfig();
    }

    /**
     * Check whether an action should be run in idle.
     *
     * @param {number} [totalCount] A total record count.
     * @returns {boolean}
     */
    checkIsIdle(totalCount) {
        if (this.view.getUser().isPortal()) {
            return false;
        }

        if (typeof totalCount === 'undefined') {
            totalCount = this.view.options.totalCount;
        }

        if (typeof totalCount === 'undefined' && this.view.collection) {
            totalCount = this.view.collection.total;
        }

        return totalCount === -1 || totalCount > this.config.get('massActionIdleCountThreshold');
    }

    /**
     * Process.
     *
     * @param {string} id An ID.
     * @param {string} action An action.
     * @returns {Promise<module:view>} Resolves with a dialog view.
     *   The view emits the 'close:success' event.
     */
    process(id, action) {
        Espo.Ui.notify(false);

        return new Promise(resolve => {
            this.view
                .createView('dialog', 'views/modals/mass-action', {
                    id: id,
                    action: action,
                    scope: this.view.scope || this.view.entityType,
                })
                .then(view => {
                    view.render();

                    resolve(view);

                    this.view.listenToOnce(view, 'success', data => {
                        resolve(data);

                        this.view.listenToOnce(view, 'close', () => {
                            view.trigger('close:success', data);
                        });
                    });
                });
        });
    }
}

export default MassActionHelper;
PK]�P60NNhelpers/export.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/**
 * An export helper.
 */
class ExportHelper {

    /**
     * @param {module:view} view A view.
     */
    constructor(view) {
        /**
         * @private
         * @type {module:view}
         */
        this.view = view;

        /**
         * @private
         * @type {module:models/settings}
         */
        this.config = view.getConfig();
    }

    /**
     * Check whether an export should be run in idle.
     *
     * @param {number} totalCount A total record count.
     * @returns {boolean}
     */
    checkIsIdle(totalCount) {
        if (this.view.getUser().isPortal()) {
            return false;
        }

        if (typeof totalCount === 'undefined') {
            totalCount = this.view.options.totalCount;
        }

        return totalCount === -1 || totalCount > this.config.get('exportIdleCountThreshold');
    }

    /**
     * Process export.
     *
     * @param {string} id An ID.
     * @returns {Promise<module:view>} Resolves with a dialog view.
     *   The view emits the 'close:success' event.
     */
    process(id) {
        Espo.Ui.notify(false);

        return new Promise(resolve => {
            this.view.createView('dialog', 'views/export/modals/idle', {id: id})
                .then(view => {
                    view.render();

                    resolve(view);

                    this.view.listenToOnce(view, 'success', data => {
                        resolve(data);

                        this.view.listenToOnce(view, 'close', () => {
                            view.trigger('close:success', data);
                        });
                    });
                });
        });
    }
}

export default ExportHelper;
PK]�_}I��#helpers/model/defaults-populator.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/**
 * Defaults populator.
 */
class DefaultsPopulator {

    /**
     * @param {module:models/user} user
     * @param {module:models/preferences} preferences
     * @param {module:acl-manager} acl
     * @param {module:models/settings} config
     */
    constructor(user, preferences, acl, config) {
        this.user = user;
        this.preferences = preferences;
        this.acl = acl;
        this.config = config;
    }

    /**
     * Populate default values.
     *
     * @param {module:model} model A model.
     */
    populate(model) {
        model.populateDefaults();

        let defaultHash = {};

        if (!this.user.isPortal()) {
            this.prepare(model, defaultHash);
        }

        if (this.user.isPortal()) {
            this.prepareForPortal(model, defaultHash);
        }

        for (let attr in defaultHash) {
            if (model.has(attr)) {
                delete defaultHash[attr];
            }
        }

        model.set(defaultHash, {silent: true});
    }

    /**
     * @param {module:model} model
     * @param {Object.<string, *>} defaultHash
     * @private
     */
    prepare(model, defaultHash) {
        let hasAssignedUsers =
            model.hasField('assignedUsers') &&
            model.getLinkParam('assignedUsers', 'entity') === 'User';

        if (model.hasField('assignedUser') || hasAssignedUsers) {
            let assignedUserField = 'assignedUser';

            if (hasAssignedUsers) {
                assignedUserField = 'assignedUsers';
            }

            let fillAssignedUser = true;

            if (this.preferences.get('doNotFillAssignedUserIfNotRequired')) {
                fillAssignedUser = false;

                if (model.getFieldParam(assignedUserField, 'required')) {
                    fillAssignedUser = true;
                }
                else if (this.acl.getPermissionLevel('assignmentPermission') === 'no') {
                    fillAssignedUser = true;
                }
                else if (
                    this.acl.getPermissionLevel('assignmentPermission') === 'team' &&
                    !this.user.get('defaultTeamId')
                ) {
                    fillAssignedUser = true;
                }
                else if (
                    this.acl.getScopeForbiddenFieldList(model.entityType, 'edit').includes(assignedUserField)
                ) {
                    fillAssignedUser = true;
                }
            }

            if (fillAssignedUser) {
                if (hasAssignedUsers) {
                    defaultHash['assignedUsersIds'] = [this.user.id];
                    defaultHash['assignedUsersNames'] = {};
                    defaultHash['assignedUsersNames'][this.user.id] = this.user.get('name');
                }
                else {
                    defaultHash['assignedUserId'] = this.user.id;
                    defaultHash['assignedUserName'] = this.user.get('name');
                }
            }
        }

        let defaultTeamId = this.user.get('defaultTeamId');

        if (defaultTeamId) {
            if (
                model.hasField('teams') &&
                !model.getFieldParam('teams', 'default') &&
                Espo.Utils.lowerCaseFirst(model.getLinkParam('teams', 'relationName') || '') === 'entityTeam'
            ) {
                defaultHash['teamsIds'] = [defaultTeamId];
                defaultHash['teamsNames'] = {};
                defaultHash['teamsNames'][defaultTeamId] = this.user.get('defaultTeamName');
            }
        }
    }

    /**
     * @param {module:model} model
     * @param {Object.<string, *>} defaultHash
     * @private
     */
    prepareForPortal(model, defaultHash) {
        if (
            model.hasField('account') &&
            ['belongsTo', 'hasOne'].includes(model.getLinkType('account')) &&
            model.getLinkParam('account', 'entity') === 'Account'
        ) {
            if (this.user.get('accountId')) {
                defaultHash['accountId'] =  this.user.get('accountId');
                defaultHash['accountName'] = this.user.get('accountName');
            }
        }

        if (
            model.hasField('contact') &&
            ['belongsTo', 'hasOne'].includes(model.getLinkType('contact'))&&
            model.getLinkParam('contact', 'entity') === 'Contact'
        ) {
            if (this.user.get('contactId')) {
                defaultHash['contactId'] = this.user.get('contactId');
                defaultHash['contactName'] = this.user.get('contactName');
            }
        }

        if (model.hasField('parent') && model.getLinkType('parent') === 'belongsToParent') {
            if (!this.config.get('b2cMode')) {
                if (this.user.get('accountId')) {
                    if ((model.getFieldParam('parent', 'entityList') || []).includes('Account')) {
                        defaultHash['parentId'] = this.user.get('accountId');
                        defaultHash['parentName'] = this.user.get('accountName');
                        defaultHash['parentType'] = 'Account';
                    }
                }
            }
            else {
                if (this.user.get('contactId')) {
                    if ((model.getFieldParam('parent', 'entityList') || []).includes('Contact')) {
                        defaultHash['contactId'] = this.user.get('contactId');
                        defaultHash['parentName'] = this.user.get('contactName');
                        defaultHash['parentType'] = 'Contact';
                    }
                }
            }
        }

        if (
            model.hasField('accounts') &&
            model.getLinkType('accounts') === 'hasMany' &&
            model.getLinkParam('accounts', 'entity') === 'Account'
        ) {
            if (this.user.get('accountsIds')) {
                defaultHash['accountsIds'] = this.user.get('accountsIds');
                defaultHash['accountsNames'] = this.user.get('accountsNames');
            }
        }

        if (
            model.hasField('contacts') &&
            model.getLinkType('contacts') === 'hasMany'&&
            model.getLinkParam('contacts', 'entity') === 'Contact'
        ) {
            if (this.user.get('contactId')) {
                defaultHash['contactsIds'] = [this.user.get('contactId')];

                let names = {};

                names[this.user.get('contactId')] = this.user.get('contactName');
                defaultHash['contactsNames'] = names;
            }
        }
    }
}

export default DefaultsPopulator;
PK]8�����helpers/list/select-provider.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

class SelectProvider {

    /**
     * @param {module:layout-manager} layoutManager
     * @param {module:metadata} metadata
     * @param {module:field-manager} fieldManager
     */
    constructor(layoutManager, metadata, fieldManager) {
        this.layoutManager = layoutManager;
        this.metadata = metadata;
        this.fieldManager = fieldManager;
    }

    /**
     * Get select attributes.
     *
     * @param {string} entityType
     * @param {string} [layoutName='list']
     * @return {Promise<string[]>}
     */
    get(entityType, layoutName) {
        return new Promise(resolve => {
            this.layoutManager.get(entityType, layoutName || 'list', layout => {
                let list = this.getFromLayout(entityType, layout);

                resolve(list);
            });
        });
    }

    /**
     * Get select attributes from a layout.
     *
     * @param {string} entityType
     * @param {module:views/record/list~columnDefs[]} listLayout
     * @return {string[]}
     */
    getFromLayout(entityType, listLayout) {
        let list = [];

        listLayout.forEach(item => {
            if (!item.name) {
                return;
            }

            let field = item.name;
            let fieldType = this.metadata.get(['entityDefs', entityType, 'fields', field, 'type']);

            if (!fieldType) {
                return;
            }

            list = [
                this.fieldManager.getEntityTypeFieldAttributeList(entityType, field),
                ...list
            ];
        });

        return list;
    }
}

export default SelectProvider;
PK]p)�helpers/file-upload.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module helpers/file-upload */

/**
 * A file-upload helper.
 */
class FileUploadExport {

    /**
     * @param {module:models/settings} config A config.
     */
    constructor(config) {
        /**
         * @private
         * @type {module:models/settings}
         */
        this.config = config;
    }

    /**
     * @typedef {Object} module:helpers/file-upload~Options
     *
     * @property {function(number):void} [afterChunkUpload] After every chunk is uploaded.
     * @property {function(module:model):void} [afterAttachmentSave] After an attachment is saved.
     * @property {{isCanceled?: boolean}} [mediator] A mediator.
     */

    /**
     * Upload.
     *
     * @param {File} file A file.
     * @param {module:model} attachment An attachment model.
     * @param {module:helpers/file-upload~Options} [options] Options.
     * @returns {Promise}
     */
    upload(file, attachment, options) {
        options = options || {};

        options.afterChunkUpload = options.afterChunkUpload || (() => {});
        options.afterAttachmentSave = options.afterAttachmentSave || (() => {});
        options.mediator = options.mediator || {};

        attachment.set('name', file.name);
        attachment.set('type', file.type || 'text/plain');
        attachment.set('size', file.size);

        if (this._useChunks(file)) {
            return this._uploadByChunks(file, attachment, options);
        }

        return new Promise((resolve, reject) => {
            let fileReader = new FileReader();

            fileReader.onload = (e) => {
                attachment.set('file', e.target.result);

                attachment
                    .save({}, {timeout: 0})
                    .then(() => resolve())
                    .catch(() => reject());
            };

            fileReader.readAsDataURL(file);
        });
    }

    /**
     * @private
     */
    _uploadByChunks(file, attachment, options) {
        return new Promise((resolve, reject) => {
            attachment.set('isBeingUploaded', true);

            attachment
                .save()
                .then(() => {
                    options.afterAttachmentSave(attachment);

                    return this._uploadChunks(
                        file,
                        attachment,
                        resolve,
                        reject,
                        options
                    );
                })
                .catch(() => reject());
        });
    }

    /**
     * @private
     */
    _uploadChunks(file, attachment, resolve, reject, options, start) {
        start = start || 0;
        let end = start + this._getChunkSize() + 1;

        if (end > file.size) {
            end = file.size;
        }

        if (options.mediator.isCanceled) {
            reject();

            return;
        }

        let blob = file.slice(start, end);

        let fileReader = new FileReader();

        fileReader.onloadend = (e) => {
            if (e.target.readyState !== FileReader.DONE) {
                return;
            }

            Espo.Ajax
                .postRequest('Attachment/chunk/' + attachment.id, e.target.result, {
                    headers: {
                        contentType: 'multipart/form-data',
                    }
                })
                .then(() => {
                    options.afterChunkUpload(end);

                    if (end === file.size) {
                        resolve();

                        return;
                    }

                    this._uploadChunks(
                        file,
                        attachment,
                        resolve,
                        reject,
                        options,
                        end
                    );
                })
                .catch(() => reject());
        };

        fileReader.readAsDataURL(blob);
    }

    /**
     * @private
     */
    _useChunks(file) {
        let chunkSize = this._getChunkSize();

        if (!chunkSize) {
            return false;
        }

        if (file.size > chunkSize) {
            return true;
        }

        return false;
    }

    /**
     * @private
     */
    _getChunkSize() {
        return (this.config.get('attachmentUploadChunkSize') || 0) * 1024 * 1024;
    }
}

export default FileUploadExport;
PK]�%�4helpers/reg-exp-pattern.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/**
 * A regular expression pattern helper.
 */
class RegExpPatternHelper {

    /**
     * @param {module:metadata} metadata
     * @param {module:language} language
     */
    constructor(metadata, language) {
        /**
         * @private
         * @type {module:metadata}
         */
        this.metadata = metadata;
        /**
         * @private
         * @type {module:language}
         */
        this.language = language;
    }

    /**
     *
     * @param {string} pattern
     * @param {string|null} value
     * @param {string} [field]
     * @param {string} [entityType]
     * @return {{message: string}|null}
     */
    validate(pattern, value, field, entityType) {
        if (value === '' || value === null) {
            return null;
        }

        let messageKey = 'fieldNotMatchingPattern';

        if (pattern[0] === '$') {
            let patternName = pattern.slice(1);
            let foundPattern = this.metadata.get(['app', 'regExpPatterns', patternName, 'pattern']);

            if (foundPattern) {
                messageKey += '$' + patternName;
                pattern = foundPattern;
            }
        }

        let regExp = new RegExp('^' + pattern + '$');

        if (regExp.test(value)) {
            return null;
        }

        let message = this.language.translate(messageKey, 'messages')
            .replace('{pattern}', pattern);

        if (field && entityType) {
            message = message.replace('{field}', this.language.translate(field, 'fields', entityType));
        }

        return {message: message};
    }
}

export default RegExpPatternHelper;
PK]Z,'�����ui.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module ui */

import {marked} from 'marked';
import DOMPurify from 'dompurify';
import $ from 'jquery';

/**
 * Dialog parameters.
 *
 * @typedef {Object} module:ui.Dialog~Params
 *
 * @property {string} [className='dialog'] A class-name or multiple space separated.
 * @property {'static'|true|false} [backdrop='static'] A backdrop.
 * @property {boolean} [closeButton=true] A close button.
 * @property {boolean} [collapseButton=false] A collapse button.
 * @property {string|null} [header] A header HTML.
 * @property {string} [body] A body HTML.
 * @property {number|null} [width] A width.
 * @property {boolean} [removeOnClose=true] To remove on close.
 * @property {boolean} [draggable=false] Is draggable.
 * @property {function (): void} [onRemove] An on-remove callback.
 * @property {function (): void} [onClose] An on-close callback.
 * @property {function (): void} [onBackdropClick] An on-backdrop-click callback.
 * @property {string} [container='body'] A container selector.
 * @property {boolean} [keyboard=true] Enable a keyboard control. The `Esc` key closes a dialog.
 * @property {boolean} [footerAtTheTop=false] To display a footer at the top.
 * @property {module:ui.Dialog~Button[]} [buttonList] Buttons.
 * @property {module:ui.Dialog~Button[]} [dropdownItemList] Dropdown action items.
 * @property {boolean} [fullHeight] Deprecated.
 * @property {Number} [bodyDiffHeight]
 * @property {Number} [screenWidthXs]
 */

/**
 * A button or dropdown action item.
 *
 * @typedef {Object} module:ui.Dialog~Button
 *
 * @property {string} name A name.
 * @property {boolean} [pullLeft=false] Deprecated. Use the `position` property.
 * @property {'left'|'right'} [position='left'] A position.
 * @property {string} [html] HTML.
 * @property {string} [text] A text.
 * @property {boolean} [disabled=false] Disabled.
 * @property {boolean} [hidden=false] Hidden.
 * @property {'default'|'danger'|'success'|'warning'} [style='default'] A style.
 * @property {function(Espo.Ui.Dialog, JQueryEventObject): void} [onClick] An on-click callback.
 * @property {string} [className] An additional class name.
 * @property {string} [title] A title.
 */

/**
 * @alias Espo.Ui.Dialog
 */
class Dialog {

    height
    fitHeight
    onRemove
    onClose
    onBackdropClick
    buttons
    screenWidthXs

    /**
     * @param {module:ui.Dialog~Params} options Options.
     */
    constructor(options) {
        options = options || {};

        /** @private */
        this.className = 'dialog';
        /** @private */
        this.backdrop = 'static';
        /** @private */
        this.closeButton = true;
        /** @private */
        this.collapseButton = false;
        /** @private */
        this.header = null;
        /** @private */
        this.body = '';
        /** @private */
        this.width = null;
        /**
         * @private
         * @type {module:ui.Dialog~Button[]}
         */
        this.buttonList = [];
        /**
         * @private
         * @type {module:ui.Dialog~Button[]}
         */
        this.dropdownItemList = [];
        /** @private */
        this.removeOnClose = true;
        /** @private */
        this.draggable = false;
        /** @private */
        this.container = 'body';
        /** @private */
        this.options = options;
        /** @private */
        this.keyboard = true;

        this.activeElement = document.activeElement;

        let params = [
            'className',
            'backdrop',
            'keyboard',
            'closeButton',
            'collapseButton',
            'header',
            'body',
            'width',
            'height',
            'fitHeight',
            'buttons',
            'buttonList',
            'dropdownItemList',
            'removeOnClose',
            'draggable',
            'container',
            'onRemove',
            'onClose',
            'onBackdropClick',
        ];

        params.forEach(param => {
            if (param in options) {
                this[param] = options[param];
            }
        });

        /** @private */
        this.onCloseIsCalled = false;

        if (this.buttons && this.buttons.length) {
            /**
             * @private
             * @type {module:ui.Dialog~Button[]}
             */
            this.buttonList = this.buttons;
        }

        this.id = 'dialog-' + Math.floor((Math.random() * 100000));

        if (typeof this.backdrop === 'undefined') {
            /** @private */
            this.backdrop = 'static';
        }

        let $header = this.getHeader();
        let $footer = this.getFooter();

        let $body = $('<div>')
            .addClass('modal-body body')
            .html(this.body);

        let $content = $('<div>').addClass('modal-content');

        if ($header) {
            $content.append($header);
        }

        if ($footer && this.options.footerAtTheTop) {
            $content.append($footer);
        }

        $content.append($body);

        if ($footer && !this.options.footerAtTheTop) {
            $content.append($footer);
        }

        let $dialog = $('<div>')
            .addClass('modal-dialog')
            .append($content);

        let $container = $(this.container);

        $('<div>')
            .attr('id', this.id)
            .attr('class', this.className + ' modal')
            .attr('role', 'dialog')
            .attr('tabindex', '-1')
            .append($dialog)
            .appendTo($container);

        /**
         * An element.
         *
         * @type {JQuery}
         */
        this.$el = $('#' + this.id);

        /**
         * @private
         * @type {Element}
         */
        this.el = this.$el.get(0);

        this.$el.find('header a.close').on('click', () => {
            //this.close();
        });

        this.initButtonEvents();

        if (this.draggable) {
            this.$el.find('header').css('cursor', 'pointer');

            // noinspection JSUnresolvedReference
            this.$el.draggable({
                handle: 'header',
            });
        }

        let modalContentEl = this.$el.find('.modal-content');

        if (this.width) {
            modalContentEl.css('width', this.width);
            modalContentEl.css('margin-left', '-' + (parseInt(this.width.replace('px', '')) / 5) + 'px');
        }

        if (this.removeOnClose) {
            this.$el.on('hidden.bs.modal', e => {
                if (this.$el.get(0) === e.target) {
                    if (!this.onCloseIsCalled) {
                        this.close();
                    }

                    if (this.skipRemove) {
                        return;
                    }

                    this.remove();
                }
            });
        }

        let $window = $(window);

        this.$el.on('shown.bs.modal', () => {
            $('.modal-backdrop').not('.stacked').addClass('stacked');

            let headerHeight = this.$el.find('.modal-header').outerHeight() || 0;
            let footerHeight = this.$el.find('.modal-footer').outerHeight() || 0;

            let diffHeight = headerHeight + footerHeight;

            if (!options.fullHeight) {
                diffHeight = diffHeight + options.bodyDiffHeight;
            }

            if (this.fitHeight || options.fullHeight) {
                let processResize = () => {
                    let windowHeight = window.innerHeight;
                    let windowWidth = $window.width();

                    if (!options.fullHeight && windowHeight < 512) {
                        this.$el.find('div.modal-body').css({
                            maxHeight: 'none',
                            overflow: 'auto',
                            height: 'none',
                        });

                        return;
                    }

                    let cssParams = {
                        overflow: 'auto',
                    };

                    if (options.fullHeight) {
                        cssParams.height = (windowHeight - diffHeight) + 'px';

                        this.$el.css('paddingRight', 0);
                    }
                    else {
                        if (windowWidth <= options.screenWidthXs) {
                            cssParams.maxHeight = 'none';
                        } else {
                            cssParams.maxHeight = (windowHeight - diffHeight) + 'px';
                        }
                    }

                    this.$el.find('div.modal-body').css(cssParams);
                };

                $window.off('resize.modal-height');
                $window.on('resize.modal-height', processResize);

                processResize();
            }
        });

        let $documentBody = $(document.body);

        this.$el.on('hidden.bs.modal', () => {
            if ($('.modal:visible').length > 0) {
                $documentBody.addClass('modal-open');
            }
        });
    }

    /** @private */
    callOnClose() {
        if (this.onClose) {
            this.onClose()
        }
    }

    /** @private */
    callOnBackdropClick() {
        if (this.onBackdropClick) {
            this.onBackdropClick()
        }
    }

    /** @private */
    callOnRemove() {
        if (this.onRemove) {
            this.onRemove()
        }
    }

    /**
     * Set action items.
     *
     * @param {module:ui.Dialog~Button[]} buttonList
     * @param {module:ui.Dialog~Button[]} dropdownItemList
     */
    setActionItems(buttonList, dropdownItemList) {
        this.buttonList = buttonList;
        this.dropdownItemList = dropdownItemList;
    }

    /**
     * Init button events.
     */
    initButtonEvents() {
        this.buttonList.forEach(o => {
            if (typeof o.onClick === 'function') {
                let $button = $('#' + this.id + ' .modal-footer button[data-name="' + o.name + '"]');

                $button.on('click', e => o.onClick(this, e));
            }
        });

        this.dropdownItemList.forEach(o => {
            if (typeof o.onClick === 'function') {
                let $button = $('#' + this.id + ' .modal-footer a[data-name="' + o.name + '"]');

                $button.on('click', e => o.onClick(this, e));
            }
        });
    }

    /**
     * @private
     * @return {JQuery|null}
     */
    getHeader() {
        if (!this.header) {
            return null;
        }

        let $header = $('<header />')
            .addClass('modal-header')
            .addClass(this.options.fixedHeaderHeight ? 'fixed-height' : '')
            .append(
                $('<h4 />')
                    .addClass('modal-title')
                    .append(
                        $('<span />')
                            .addClass('modal-title-text')
                            .html(this.header)
                    )
            );


        if (this.collapseButton) {
            $header.prepend(
                $('<a>')
                    .addClass('collapse-button')
                    .attr('role', 'button')
                    .attr('tabindex', '-1')
                    .attr('data-action', 'collapseModal')
                    .append(
                        $('<span />')
                            .addClass('fas fa-minus')
                    )
            );
        }

        if (this.closeButton) {
            $header.prepend(
                $('<a>')
                    .addClass('close')
                    .attr('data-dismiss', 'modal')
                    .attr('role', 'button')
                    .attr('tabindex', '-1')
                    .append(
                        $('<span />')
                            .attr('aria-hidden', 'true')
                            .html('&times;')
                    )
            );
        }

        return $header;
    }

    /**
     * Get a footer.
     *
     * @return {JQuery|null}
     */
    getFooter() {
        if (!this.buttonList.length && !this.dropdownItemList.length) {
            return null;
        }

        let $footer = $('<footer>').addClass('modal-footer');

        let $main = $('<div>')
            .addClass('btn-group')
            .addClass('main-btn-group');

        let $additional = $('<div>')
            .addClass('btn-group')
            .addClass('additional-btn-group');

        this.buttonList.forEach(/** module:ui.Dialog~Button */o => {
            let style = o.style || 'default';

            let $button =
                $('<button>')
                    .attr('type', 'button')
                    .attr('data-name', o.name)
                    .addClass('btn')
                    .addClass('btn-' + style)
                    .addClass(o.className || 'btn-xs-wide')

            if (o.disabled) {
                $button.attr('disabled', 'disabled');
                $button.addClass('disabled');
            }

            if (o.hidden) {
                $button.addClass('hidden');
            }

            if (o.title) {
                $button.attr('title', o.title);
            }

            if (o.text) {
                $button.text(o.text);
            }

            if (o.html) {
                $button.html(o.html);
            }

            if (o.pullLeft || o.position === 'right') {
                $additional.append($button);

                return;
            }

            $main.append($button);
        });

        let allDdItemsHidden = this.dropdownItemList.filter(o => !o.hidden).length === 0;

        let $dropdown = $('<div>')
            .addClass('btn-group')
            .addClass(allDdItemsHidden ? 'hidden' : '')
            .append(
                $('<button>')
                    .attr('type', 'button')
                    .addClass('btn btn-default dropdown-toggle')
                    .addClass(allDdItemsHidden ? 'hidden' : '')
                    .attr('data-toggle', 'dropdown')
                    .append(
                        $('<span>').addClass('fas fa-ellipsis-h')
                    )
            );

        let $ul = $('<ul>').addClass('dropdown-menu pull-right');

        $dropdown.append($ul);

        this.dropdownItemList.forEach(/** module:ui.Dialog~Button */o => {
            let $a = $('<a>')
                .attr('role', 'button')
                .attr('tabindex', '0')
                .attr('data-name', o.name);

            if (o.text) {
                $a.text(o.text);
            }

            if (o.title) {
                $a.attr('title', o.title);
            }

            if (o.html) {
                $a.html(o.html);
            }

            let $li = $('<li>')
                .addClass(o.hidden ? ' hidden' : '')
                .append($a)

            $ul.append($li);
        });

        if ($ul.children().length) {
            $main.append($dropdown);
        }

        if ($additional.children().length) {
            $footer.append($additional);
        }

        $footer.append($main);

        return $footer;
    }

    /**
     * Show.
     */
    show() {
        // noinspection JSUnresolvedReference
        this.$el.modal({
             backdrop: this.backdrop,
             keyboard: this.keyboard,
        });

        this.$el.find('.modal-content').removeClass('hidden');

        let $modalBackdrop = $('.modal-backdrop');

        $modalBackdrop.each((i, el) => {
            if (i < $modalBackdrop.length - 1) {
                $(el).addClass('hidden');
            }
        });

        let $modalContainer = $('.modal-container');

        $modalContainer.each((i, el) => {
            if (i < $modalContainer.length - 1) {
                $(el).addClass('overlaid');
            }
        });

        this.$el.off('click.dismiss.bs.modal');

        this.$el.on(
            'click.dismiss.bs.modal',
            '> div.modal-dialog > div.modal-content > header [data-dismiss="modal"]',
            () => this.close()
        );

        this.$el.on('mousedown', e => {
            this.$mouseDownTarget = $(e.target);
        });

        this.$el.on('click.dismiss.bs.modal', (e) => {
            if (e.target !== e.currentTarget) {
                return;
            }

            if (
                this.$mouseDownTarget &&
                this.$mouseDownTarget.closest('.modal-content').length
            ) {
                return;
            }

            this.callOnBackdropClick();

            if (this.backdrop === 'static') {
                return;
            }

            this.close();
        });

        $('body > .popover').addClass('hidden');
    }

    /**
     * Hide.
     */
    hide() {
        this.$el.find('.modal-content').addClass('hidden');
    }

    /**
     * Hide with a backdrop.
     */
    hideWithBackdrop() {
        let $modalBackdrop = $('.modal-backdrop');

        $modalBackdrop.last().addClass('hidden');
        $($modalBackdrop.get($modalBackdrop.length - 2)).removeClass('hidden');

        let $modalContainer = $('.modal-container');

        $($modalContainer.get($modalContainer.length - 2)).removeClass('overlaid');

        this.skipRemove = true;

        setTimeout(() => {
            this.skipRemove = false;
        }, 50);

        // noinspection JSUnresolvedReference
        this.$el.modal('hide');
        this.$el.find('.modal-content').addClass('hidden');
    }

    /**
     * @private
     */
    _close() {
        let $modalBackdrop = $('.modal-backdrop');

        $modalBackdrop.last().removeClass('hidden');

        let $modalContainer = $('.modal-container');

        $($modalContainer.get($modalContainer.length - 2)).removeClass('overlaid');
    }

    /**
     * @private
     * @param {Element} element
     * @return {Element|null}
     */
    _findClosestFocusableElement(element) {
        // noinspection JSUnresolvedReference
        let isVisible = !!(
            element.offsetWidth ||
            element.offsetHeight ||
            element.getClientRects().length
        );

        if (isVisible) {
            // noinspection JSUnresolvedReference
            element.focus({preventScroll: true});

            return element;
        }

        let $element = $(element);

        if ($element.closest('.dropdown-menu').length) {
            let $button = $element.closest('.btn-group').find(`[data-toggle="dropdown"]`);


            if ($button.length) {
                // noinspection JSUnresolvedReference
                $button.get(0).focus({preventScroll: true});

                return $button.get(0);
            }
        }

        return null;
    }

    /**
     * Close.
     */
    close() {
        if (!this.onCloseIsCalled) {
            this.callOnClose();
            this.onCloseIsCalled = true;

            if (this.activeElement) {
                setTimeout(() => {
                    let element = this._findClosestFocusableElement(this.activeElement);

                    if (element) {
                        // noinspection JSUnresolvedReference
                        element.focus({preventScroll: true});
                    }
                }, 50);
            }
        }

        this._close();
        // noinspection JSUnresolvedReference
        this.$el.modal('hide');
        $(this).trigger('dialog:close');
    }

    /**
     * Remove.
     */
    remove() {
        this.callOnRemove();

        // Hack allowing multiple backdrops.
        // `close` function may be called twice.
        this._close();
        this.$el.remove();

        $(this).off();
        $(window).off('resize.modal-height');
    }
}


/**
 * UI utils.
 */
Espo.Ui = {

    Dialog: Dialog,

    /**
     * @typedef {Object} Espo.Ui~ConfirmOptions
     *
     * @property {string} confirmText A confirm-button text.
     * @property {string} cancelText A cancel-button text.
     * @property {'danger'|'success'|'warning'|'default'} [confirmStyle='danger']
     *   A confirm-button style.
     * @property {'static'|boolean} [backdrop=false] A backdrop.
     * @property {function():void} [cancelCallback] A cancel-callback.
     * @property {boolean} [isHtml=false] Whether the message is HTML.
     */

    /**
     * Show a confirmation dialog.
     *
     * @param {string} message A message.
     * @param {Espo.Ui~ConfirmOptions|{}} o Options.
     * @param {function} [callback] Deprecated. Use a promise.
     * @param {Object} [context] Deprecated.
     * @returns {Promise} Resolves if confirmed.
     */
    confirm: function (message, o, callback, context) {
        o = o || {};

        let confirmText = o.confirmText;
        let cancelText = o.cancelText;
        let confirmStyle = o.confirmStyle || 'danger';
        let backdrop = o.backdrop;

        if (typeof backdrop === 'undefined') {
            backdrop = false;
        }

        let isResolved = false;

        let processCancel = () => {
            if (!o.cancelCallback) {
                return;
            }

            if (context) {
                o.cancelCallback.call(context);

                return;
            }

            o.cancelCallback();
        };

        if (!o.isHtml) {
            message = Handlebars.Utils.escapeExpression(message);
        }

        return new Promise(resolve => {
            let dialog = new Dialog({
                backdrop: backdrop,
                header: null,
                className: 'dialog-confirm',
                body: '<span class="confirm-message">' + message + '</a>',
                buttonList: [
                    {
                        text: ' ' + confirmText + ' ',
                        name: 'confirm',
                        className: 'btn-s-wide',
                        onClick: () => {
                            isResolved = true;

                            if (callback) {
                                if (context) {
                                    callback.call(context);
                                } else {
                                    callback();
                                }
                            }

                            resolve();

                            dialog.close();
                        },
                        style: confirmStyle,
                        position: 'right',
                    },
                    {
                        text: cancelText,
                        name: 'cancel',
                        className: 'btn-s-wide',
                        onClick: () => {
                            isResolved = true;

                            dialog.close();
                            processCancel();
                        },
                        position: 'left',
                    }
                ],
                onClose: () => {
                    if (isResolved) {
                        return;
                    }

                    processCancel();
                },
            });

            dialog.show();
            dialog.$el.find('button[data-name="confirm"]').focus();
        });
    },

    /**
     * Create a dialog.
     *
     * @param {module:ui.Dialog~Params} options Options.
     * @returns {Dialog}
     */
    dialog: function (options) {
        return new Dialog(options);
    },


    /**
     * Popover options.
     *
     * @typedef {Object} Espo.Ui~PopoverOptions
     *
     * @property {'bottom'|'top'|'left'|'right'} [placement='bottom'] A placement.
     * @property {string|JQuery} [container] A container selector.
     * @property {string} [content] An HTML content.
     * @property {string} [text] A text.
     * @property {'manual'|'click'|'hover'|'focus'} [trigger='manual'] A trigger type.
     * @property {boolean} [noToggleInit=false] Skip init toggle on click.
     * @property {boolean} [preventDestroyOnRender=false] Don't destroy on re-render.
     * @property {boolean} [noHideOnOutsideClick=false] Don't hide on clicking outside.
     * @property {function(): void} [onShow] On-show callback.
     * @property {function(): void} [onHide] On-hide callback.
     */

    /**
     * Init a popover.
     *
     * @param {Element|JQuery} element An element.
     * @param {Espo.Ui~PopoverOptions} o Options.
     * @param {module:view} [view] A view.
     * @return {{hide: function(), destroy: function(), show: function(), detach: function()}}
     */
    popover: function (element, o, view) {
        const $el = $(element);
        const $body = $('body');
        const content = o.content || Handlebars.Utils.escapeExpression(o.text || '');
        let isShown = false;

        let container = o.container;

        if (!container) {
            const $modalBody = $el.closest('.modal-body');

            container = $modalBody.length ? $modalBody : 'body';
        }

        // noinspection JSUnresolvedReference
        $el
            .popover({
                placement: o.placement || 'bottom',
                container: container,
                viewport: container,
                html: true,
                content: content,
                trigger: o.trigger || 'manual',
            })
            .on('shown.bs.popover', () => {
                isShown = true;

                if (!view) {
                    return;
                }

                if (view && !o.noHideOnOutsideClick) {
                    $body.off('click.popover-' + view.cid);

                    $body.on('click.popover-' + view.cid, e => {
                        if ($(e.target).closest('.popover-content').get(0)) {
                            return;
                        }

                        if ($.contains($el.get(0), e.target)) {
                            return;
                        }

                        if ($el.get(0) === e.target) {
                            return;
                        }

                        $body.off('click.popover-' + view.cid);
                        // noinspection JSUnresolvedReference
                        $el.popover('hide');
                    });
                }

                if (o.onShow) {
                    o.onShow();
                }
            })
            .on('hidden.bs.popover', () => {
                isShown = false;

                if (o.onHide) {
                    o.onHide();
                }
            });

        if (!o.noToggleInit) {
            $el.on('click', () => {
                // noinspection JSUnresolvedReference
                $el.popover('toggle');
            });
        }

        let isDetached = false;

        const detach = () => {
            if (view) {
                $body.off('click.popover-' + view.cid);

                view.off('remove', destroy);
                view.off('render', destroy);
                view.off('render', hide);
            }

            isDetached = true;
        };

        const destroy = () => {
            if (isDetached) {
                return;
            }

            // noinspection JSUnresolvedReference
            $el.popover('destroy');

            detach();
        };

        const hide = () => {
            if (!isShown) {
                return;
            }

            // noinspection JSUnresolvedReference
            $el.popover('hide');
        };

        const show = () => {
            // noinspection JSUnresolvedReference
            $el.popover('show');
        };

        if (view) {
            view.once('remove', destroy);

            if (!o.preventDestroyOnRender) {
                view.once('render', destroy);
            }

            if (o.preventDestroyOnRender) {
                view.on('render', hide);
            }
        }

        return {
            hide: () => hide(),
            destroy: () => destroy(),
            show: () => show(),
            detach: () => detach(),
        };
    },

    /**
     * Notify options.
     *
     * @typedef {Object} Espo.Ui~NotifyOptions
     * @property {boolean} [closeButton] A close button.
     * @property {boolean} [suppress] Suppress other warning alerts while this is displayed.
     */

    /**
     * Show a notify-message.
     *
     * @param {string|false} message A message. False removes an already displayed message.
     * @param {'warning'|'danger'|'success'|'info'} [type='warning'] A type.
     * @param {number} [timeout] Microseconds. If empty, then won't be hidden.
     *   Should be hidden manually or by displaying another message.
     * @param {Espo.Ui~NotifyOptions} [options] Options.
     */
    notify: function (message, type, timeout, options) {
        type = type || 'warning';
        options = {...options};

        if (type === 'warning' && notifySuppressed) {
            return;
        }

        $('#notification').remove();

        if (!message) {
            return;
        }

        if (options.suppress && timeout) {
            notifySuppressed = true;

            setTimeout(() => notifySuppressed = false, timeout)
        }

        let parsedMessage = message.indexOf('\n') !== -1 ?
            marked.parse(message) :
            marked.parseInline(message);

        let sanitizedMessage = DOMPurify.sanitize(parsedMessage, {}).toString();

        let closeButton = options.closeButton || false;

        if (type === 'error') {
            // For bc.
            type = 'danger';
        }

        if (sanitizedMessage === ' ... ') {
            sanitizedMessage = ' <span class="fas fa-spinner fa-spin"> ';
        }

        let additionalClassName = closeButton ? ' alert-closable' : '';

        let $el = $('<div>')
            .addClass('alert alert-' + type + additionalClassName + ' fade in')
            .attr('id', 'notification')
            .css({
                'position': 'fixed',
                'top': '0',
                'left': '50vw',
                'transform': 'translate(-50%, 0)',
                'z-index': 2000,
            })
            .append(
                $('<div>')
                    .addClass('message')
                    .html(sanitizedMessage)
            );

        if (closeButton) {
            let $close = $('<button>')
                .attr('type', 'button')
                .attr('data-dismiss', 'modal')
                .attr('aria-hidden', 'true')
                .addClass('close')
                .html('&times;');

            $el.append(
                $('<div>')
                    .addClass('close-container')
                    .append($close)
            );

            $close.on('click', () => $el.alert('close'));
        }

        if (timeout) {
            setTimeout(() => $el.alert('close'), timeout);
        }

        $el.appendTo('body')
    },

    /**
     * Show a warning message.
     *
     * @param {string} message A message.
     * @param {Espo.Ui~NotifyOptions} [options] Options.
     */
    warning: function (message, options) {
        Espo.Ui.notify(message, 'warning', 2000, options);
    },

    /**
     * Show a success message.
     *
     * @param {string} message A message.
     * @param {Espo.Ui~NotifyOptions} [options] Options.
     */
    success: function (message, options) {
        Espo.Ui.notify(message, 'success', 2000, options);
    },

    /**
     * Show an error message.
     *
     * @param {string} message A message.
     * @param {Espo.Ui~NotifyOptions|true} [options] Options. If true, then only closeButton option will be applied.
     */
    error: function (message, options) {
        options = typeof options === 'boolean' ?
            {closeButton: options} :
            {...options};

        let timeout = options.closeButton ? 0 : 4000;

        Espo.Ui.notify(message, 'danger', timeout, options);
    },

    /**
     * Show an info message.
     *
     * @param {string} message A message.
     * @param {Espo.Ui~NotifyOptions} [options] Options.
     */
    info: function (message, options) {
        Espo.Ui.notify(message, 'info', 2000, options);
    },
};

let notifySuppressed = false;

/**
 * @deprecated Use `Espo.Ui`.
 */
Espo.ui = Espo.Ui;

export default Espo.Ui;
PK]d��p�	�	models/user.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module models/user */

import Model from 'model';

/**
 * A user.
 */
class User extends Model {

    name = 'User'
    entityType = 'User'
    urlRoot = 'User'

    /**
     * Is admin.
     *
     * @returns {boolean}
     */
    isAdmin() {
        return this.get('type') === 'admin' || this.isSuperAdmin();
    }

    /**
     * Is portal.
     *
     * @returns {boolean}
     */
    isPortal() {
        return this.get('type') === 'portal';
    }

    /**
     * Is API.
     *
     * @returns {boolean}
     */
    isApi() {
        return this.get('type') === 'api';
    }

    /**
     * Is regular.
     *
     * @returns {boolean}
     */
    isRegular() {
        return this.get('type') === 'regular';
    }

    /**
     * Is system.
     *
     * @returns {boolean}
     */
    isSystem() {
        return this.get('type') === 'system';
    }

    /**
     * Is super-admin.
     *
     * @returns {boolean}
     */
    isSuperAdmin() {
        return this.get('type') === 'super-admin';
    }
}

export default User;
PK]=��models/preferences.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module models/preferences */

import Model from 'model';

/**
 * User preferences.
 */
class Preferences extends Model {

    name = 'Preferences'
    entityType = 'Preferences'
    urlRoot = 'Preferences'

    /**
     * Get dashlet options.
     *
     * @param {string} id A dashlet ID.
     * @returns {Object|null}
     */
    getDashletOptions(id) {
        let value = this.get('dashletsOptions') || {};

        return value[id] || null;
    }

    /**
     * Whether a user is portal.
     *
     * @returns {boolean}
     */
    isPortal() {
        return this.get('isPortalUser');
    }
}

export default Preferences;
PK]y
R
R
models/settings.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module models/settings */

import Model from 'model';

/**
 * A config.
 */
class Settings extends Model {

    name = 'Settings'
    entityType = 'Settings'
    urlRoot = 'Settings'

    /**
     * Load.
     *
     * @returns {Promise}
     */
    load() {
        return new Promise(resolve => {
            this.fetch()
                .then(() => resolve());
        });
    }

    /**
     * Get a value by a path.
     *
     * @param {string[]} path A path.
     * @returns {*} Null if not set.
     */
    getByPath(path) {
        if (!path.length) {
            return null;
        }

        let p;

        for (let i = 0; i < path.length; i++) {
            var item = path[i];

            if (i === 0) {
                p = this.get(item);
            }
            else {
                if (item in p) {
                    p = p[item];
                }
                else {
                    return null;
                }
            }

            if (i === path.length - 1) {
                return p;
            }

            if (p === null || typeof p !== 'object') {
                return null;
            }
        }
    }
}

export default Settings;
PK]|�/�cache.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module cache */

/**
 * Cache for source and resource files.
 */
class Cache {

    /**
     * @param {Number} [cacheTimestamp] A cache timestamp.
     */
    constructor(cacheTimestamp) {
        this.basePrefix = this.prefix;

        if (cacheTimestamp) {
            this.prefix =  this.basePrefix + '-' + cacheTimestamp;
        }

        if (!this.get('app', 'timestamp')) {
            this.storeTimestamp();
        }
    }

    /** @private */
    prefix = 'cache'

    /**
     * Handle actuality. Clears cache if not actual.
     *
     * @param {Number} cacheTimestamp A cache timestamp.
     */
    handleActuality(cacheTimestamp) {
        let storedTimestamp = this.getCacheTimestamp();

        if (storedTimestamp) {
            if (storedTimestamp !== cacheTimestamp) {
                this.clear();
                this.set('app', 'cacheTimestamp', cacheTimestamp);
                this.storeTimestamp();
            }

            return;
        }

        this.clear();
        this.set('app', 'cacheTimestamp', cacheTimestamp);
        this.storeTimestamp();
    }

    /**
     * Get a cache timestamp.
     *
     * @returns {number}
     */
    getCacheTimestamp() {
        return parseInt(this.get('app', 'cacheTimestamp') || 0);
    }

    /**
     * @todo Revise whether is needed.
     */
    storeTimestamp() {
        let frontendCacheTimestamp = Date.now();

        this.set('app', 'timestamp', frontendCacheTimestamp);
    }

    /**
     * @private
     * @param {string} type
     * @returns {string}
     */
    composeFullPrefix(type) {
        return this.prefix + '-' + type;
    }

    /**
     * @private
     * @param {string} type
     * @param {string} name
     * @returns {string}
     */
    composeKey(type, name) {
        return this.composeFullPrefix(type) + '-' + name;
    }

    /**
     * @private
     * @param {string} type
     */
    checkType(type) {
        if (typeof type === 'undefined' && toString.call(type) !== '[object String]') {
            throw new TypeError("Bad type \"" + type + "\" passed to Cache().");
        }
    }

    /**
     * Get a stored value.
     *
     * @param {string} type A type/category.
     * @param {string} name A name.
     * @returns {string|null} Null if no stored value.
     */
    get(type, name) {
        this.checkType(type);

        let key = this.composeKey(type, name);

        let stored;

        try {
            stored = localStorage.getItem(key);
        }
        catch (error) {
            console.error(error);

            return null;
        }

        if (stored) {
            let result = stored;

            if (stored.length > 9 && stored.substring(0, 9) === '__JSON__:') {
                let jsonString = stored.slice(9);

                try {
                    result = JSON.parse(jsonString);
                }
                catch (error) {
                    result = stored;
                }
            }

            return result;
        }

        return null;
    }

    /**
     * Store a value.
     *
     * @param {string} type A type/category.
     * @param {string} name A name.
     * @param {any} value A value.
     */
    set(type, name, value) {
        this.checkType(type);

        let key = this.composeKey(type, name);

        if (value instanceof Object || Array.isArray(value)) {
            value = '__JSON__:' + JSON.stringify(value);
        }

        try {
            localStorage.setItem(key, value);
        }
        catch (error) {
            console.log('Local storage limit exceeded.');
        }
    }

    /**
     * Clear a stored value.
     *
     * @param {string} [type] A type/category.
     * @param {string} [name] A name.
     */
    clear(type, name) {
        let reText;

        if (typeof type !== 'undefined') {
            if (typeof name === 'undefined') {
                reText = '^' + this.composeFullPrefix(type);
            }
            else {
                reText = '^' + this.composeKey(type, name);
            }
        }
        else {
            reText = '^' + this.basePrefix + '-';
        }

        let re = new RegExp(reText);

        for (let i in localStorage) {
            if (re.test(i)) {
                delete localStorage[i];
            }
        }
    }
}

export default Cache;
PK][RJY�7�7field-manager.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module field-manager */

/**
 * Utility for getting field related meta information.
 */
class FieldManager {

    /**
     * Utility for getting field related meta information.
     *
     * @param {Object} [defs] Field type definitions (metadata > fields).
     * @param {module:metadata} [metadata] Metadata.
     * @param {module:acl-manager} [acl] An ACL.
     */
    constructor(defs, metadata, acl) {

        /**
         * @typedef {Object} FieldManager~defs
         * @property {string[]} [actualFields]
         * @property {string[]} [notActualFields]
         * @property {'suffix'|'prefix'} [naming]
         * @property {Object.<string, Object.<string, *>>} [params]
         * @property {boolean} [filter]
         * @property {boolean} [notMergeable]
         * @property {string} [view]
         */


        /**
         * @public
         * @internal
         * @type {FieldManager~defs}
         */
        this.defs = defs || /** @type {FieldManager~defs} */ {};

        /**
         * @public
         * @internal
         * @type {module:metadata}
         */
        this.metadata = metadata;

        /**
         * @public
         * @internal
         * @type {module:acl-manager}
         */
        this.acl = acl;
    }

    /**
     * Get a list of parameters for a specific field type.
     *
     * @param {string} fieldType A field type.
     * @returns {Object.<string, *>[]}
     */
    getParamList(fieldType) {
        if (fieldType in this.defs) {
            return this.defs[fieldType].params || [];
        }

        return [];
    }

    /**
     * Whether search filters are allowed for a field type.
     *
     * @param {string} fieldType A field type.
     * @returns {boolean}
     */
    checkFilter(fieldType) {
        if (fieldType in this.defs) {
            if ('filter' in this.defs[fieldType]) {
                return this.defs[fieldType].filter;
            }

            return false;
        }

        return false;
    }

    /**
     * Whether a merge operation is allowed for a field type.
     *
     * @param {string} fieldType A field type.
     * @returns {boolean}
     */
    isMergeable(fieldType) {
        if (fieldType in this.defs) {
            return !this.defs[fieldType].notMergeable;
        }

        return false;
    }

    /**
     * Get a list of attributes of an entity type.
     *
     * @param {string} entityType An entity type.
     * @returns {string[]}
     */
    getEntityTypeAttributeList(entityType) {
        const list = [];

        const defs = this.metadata.get('entityDefs.' + entityType + '.fields') || {};

        Object.keys(defs).forEach(field => {
            this.getAttributeList(defs[field]['type'], field).forEach(attr => {
                if (!~list.indexOf(attr)) {
                    list.push(attr);
                }
            });
        });

        return list;
    }

    /**
     * Get a list of actual attributes by a given field type and field name.
     * Non-actual attributes contains data that for a representation-only purpose.
     * E.g. `accountId` is actual, `accountName` is non-actual.
     *
     * @param {string} fieldType A field type.
     * @param {string} fieldName A field name.
     * @returns {string[]}
     */
    getActualAttributeList(fieldType, fieldName) {
        const fieldNames = [];

        if (fieldType in this.defs) {
            if ('actualFields' in this.defs[fieldType]) {
                const actualFields = this.defs[fieldType].actualFields;

                let naming = 'suffix';

                if ('naming' in this.defs[fieldType]) {
                    naming = this.defs[fieldType].naming;
                }

                if (naming === 'prefix') {
                    actualFields.forEach(f => {
                        fieldNames.push(f + Espo.Utils.upperCaseFirst(fieldName));
                    });
                }
                else {
                    actualFields.forEach(f => {
                        fieldNames.push(fieldName + Espo.Utils.upperCaseFirst(f));
                    });
                }
            }
            else {
                fieldNames.push(fieldName);
            }
        }

        return fieldNames;
    }

    /**
     * Get a list of non-actual attributes by a given field type and field name.
     * Non-actual attributes contains data that for a representation-only purpose.
     * E.g. `accountId` is actual, `accountName` is non-actual.
     *
     * @param {string} fieldType A field type.
     * @param {string} fieldName A field name.
     * @returns {string[]}
     */
    getNotActualAttributeList(fieldType, fieldName) {
        const fieldNames = [];

        if (fieldType in this.defs) {
            if ('notActualFields' in this.defs[fieldType]) {
                const notActualFields = this.defs[fieldType].notActualFields;

                let naming = 'suffix';

                if ('naming' in this.defs[fieldType]) {
                    naming = this.defs[fieldType].naming;
                }

                if (naming === 'prefix') {
                    notActualFields.forEach(f => {
                        if (f === '') {
                            fieldNames.push(fieldName);
                        }
                        else {
                            fieldNames.push(f + Espo.Utils.upperCaseFirst(fieldName));
                        }
                    });
                }
                else {
                    notActualFields.forEach(f => {
                        fieldNames.push(fieldName + Espo.Utils.upperCaseFirst(f));
                    });
                }
            }
        }

        return fieldNames;
    }

    /**
     * Get an attribute list of a specific field.
     *
     * @param {string} entityType An entity type.
     * @param {string} field A field.
     * @returns {string[]}
     */
    getEntityTypeFieldAttributeList(entityType, field) {
        const type = this.metadata.get(['entityDefs', entityType, 'fields', field, 'type']);

        if (!type) {
            return [];
        }

        return _.union(
            this.getAttributeList(type, field),
            this._getEntityTypeFieldAdditionalAttributeList(entityType, field)
        );
    }

    /**
     * Get an actual attribute list of a specific field.
     *
     * @param {string} entityType An entity type.
     * @param {string} field A field.
     * @returns {string[]}
     */
    getEntityTypeFieldActualAttributeList(entityType, field) {
        const type = this.metadata.get(['entityDefs', entityType, 'fields', field, 'type']);

        if (!type) {
            return [];
        }

        return _.union(
            this.getActualAttributeList(type, field),
            this._getEntityTypeFieldAdditionalAttributeList(entityType, field)
        );
    }

    /**
     * @private
     */
    _getEntityTypeFieldAdditionalAttributeList(entityType, field) {
        const type = this.metadata.get(['entityDefs', entityType, 'fields', field, 'type']);

        if (!type) {
            return [];
        }

        const partList = this.metadata
            .get(['entityDefs', entityType, 'fields', field, 'additionalAttributeList']) || [];

        if (partList.length === 0) {
            return [];
        }

        const isPrefix = (this.defs[type] || {}).naming === 'prefix';

        const list = [];

        partList.forEach(item => {
            if (isPrefix) {
                list.push(item + Espo.Utils.upperCaseFirst(field));

                return;
            }

            list.push(field + Espo.Utils.upperCaseFirst(item));
        });

        return list;
    }

    /**
     * Get a list of attributes by a given field type and field name.
     *
     * @param {string} fieldType A field type.
     * @param {string} fieldName A field name.
     * @returns {string[]}
     */
    getAttributeList(fieldType, fieldName) {
        return _.union(
            this.getActualAttributeList(fieldType, fieldName),
            this.getNotActualAttributeList(fieldType, fieldName)
        );
    }

    /**
     * @typedef {Object} module:field-manager~FieldFilters
     *
     * @property {string} [type] Only of a specific field type.
     * @property {string[]} [typeList] Only of a specific field types.
     * @property {boolean} [onlyAvailable] To exclude disabled, admin-only, internal, forbidden fields.
     * @property {'read'|'edit'} [acl] To exclude fields not accessible for a current user over
     *   a specified access level.
     */

    /**
     * Get a list of fields of a specific entity type.
     *
     * @param {string} entityType An entity type.
     * @param {module:field-manager~FieldFilters} [o] Filters.
     * @returns {string[]}
     */
    getEntityTypeFieldList(entityType, o) {
        let list = Object.keys(this.metadata.get(['entityDefs', entityType, 'fields']) || {});

        o = o || {};

        let typeList = o.typeList;

        if (!typeList && o.type) {
            typeList = [o.type];
        }

        if (typeList) {
            list = list.filter(item => {
                const type = this.metadata.get(['entityDefs', entityType, 'fields', item, 'type']);

                return ~typeList.indexOf(type);
            });
        }

        if (o.onlyAvailable || o.acl) {
            list = list.filter(item => {
                return this.isEntityTypeFieldAvailable(entityType, item);
            });
        }

        if (o.acl) {
            const level = o.acl || 'read';

            const forbiddenEditFieldList = this.acl.getScopeForbiddenFieldList(entityType, level);

            list = list.filter(item => {
                return !~forbiddenEditFieldList.indexOf(item);
            });
        }

        return list;
    }

    /**
     * @deprecated Since v5.7.
     */
    getScopeFieldList(entityType) {
        return this.getEntityTypeFieldList(entityType);
    }

    /**
     * Get a field parameter value.
     *
     * @param {string} entityType An entity type.
     * @param {string} field A field name.
     * @param {string} param A parameter name.
     * @returns {*}
     */
    getEntityTypeFieldParam(entityType, field, param) {
        return this.metadata.get(['entityDefs', entityType, 'fields', field, param]);
    }

    /**
     * Get a view name/path for a specific field type.
     *
     * @param {string} fieldType A field type.
     * @returns {string}
     */
    getViewName(fieldType) {
        if (fieldType in this.defs) {
            if ('view' in this.defs[fieldType]) {
                return this.defs[fieldType].view;
            }
        }

        return 'views/fields/' + Espo.Utils.camelCaseToHyphen(fieldType);
    }

    /**
     * @deprecated Use `getParamList`.
     */
    getParams(fieldType) {
        return this.getParamList(fieldType);
    }

    /**
     * @deprecated Use `getAttributeList`.
     */
    getAttributes(fieldType, fieldName) {
        return this.getAttributeList(fieldType, fieldName);
    }

    /**
     * @deprecated Use `getActualAttributeList`.
     */
    getActualAttributes(fieldType, fieldName) {
        return this.getActualAttributeList(fieldType, fieldName);
    }

    /**
     * @deprecated Use `getNotActualAttributeList`.
     */
    getNotActualAttributes(fieldType, fieldName) {
        return this.getNotActualAttributeList(fieldType, fieldName);
    }

    /**
     * Check whether a field is not disabled, not utility, not only-admin, not forbidden and not internal.
     *
     * @param {string} entityType An entity type.
     * @param {string} field A field name.
     * @returns {boolean}
     */
    isEntityTypeFieldAvailable(entityType, field) {
        const defs = this.metadata.get(['entityDefs', entityType, 'fields', field]) || {};

        if (
            defs.disabled ||
            defs.utility
        ) {
            return false;
        }

        const aclDefs = this.metadata.get(['entityAcl', entityType, 'fields', field]) || {};

        if (
            aclDefs.onlyAdmin ||
            aclDefs.forbidden ||
            aclDefs.internal
        ) {
            return false;
        }

        return true;
    }

    /**
     * @deprecated Use `isEntityTypeFieldAvailable`.
     */
    isScopeFieldAvailable(entityType, field) {
        return this.isEntityTypeFieldAvailable(entityType, field);
    }
}

export default FieldManager;
PK]O�Aˍ�
page-title.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in  the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module page-title */

import $ from 'jquery';

/**
 * A page-title util.
 */
class PageTitle {

    /**
     * @class
     * @param {module:models/settings} config A config.
     */
    constructor(config) {

        /**
         * @private
         * @type {boolean}
         */
        this.displayNotificationNumber = config.get('newNotificationCountInTitle') || false;

        /**
         * @private
         * @type {string}
         */
        this.title = $('head title').text() || '';

        /**
         * @private
         * @type {number}
         */
        this.notificationNumber = 0;
    }

    /**
     * Set a title.
     *
     * @param {string} title A title.
     */
    setTitle(title) {
        this.title = title;

        this.update();
    }

    /**
     * Set a notification number.
     *
     * @param {number} notificationNumber A number.
     */
    setNotificationNumber(notificationNumber) {
        this.notificationNumber = notificationNumber;

        if (this.displayNotificationNumber) {
            this.update();
        }
    }

    /**
     * Update a page title.
     */
    update() {
        let value = '';

        if (this.displayNotificationNumber && this.notificationNumber) {
            value = '(' + this.notificationNumber.toString() + ')';

            if (this.title) {
                value += ' ';
            }
        }

        value += this.title;

        $('head title').text(value);
    }
}

export default PageTitle;
PK]\��>>collection-factory.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module collection-factory */

/**
 * A collection factory.
 */
class CollectionFactory {
    /**
     * @param {module:model-factory} modelFactory
     * @param {module:models/settings} config
     * @param {module:metadata} metadata
     */
    constructor(modelFactory, config, metadata) {
        /** @private */
        this.modelFactory = modelFactory;
        /** @private */
        this.metadata = metadata;
        /** @private */
        this.recordListMaxSizeLimit = config.get('recordListMaxSizeLimit') || 200;
    }

    /**
     * Create a collection.
     *
     * @param {string} entityType An entity type.
     * @param {Function} [callback] Deprecated.
     * @param {Object} [context] Deprecated.
     * @returns {Promise<module:collection>}
     */
    create(entityType, callback, context) {
        return new Promise(resolve => {
            context = context || this;

            this.modelFactory.getSeed(entityType, Model => {
                let orderBy = this.modelFactory.metadata
                    .get(['entityDefs', entityType, 'collection', 'orderBy']);

                let order = this.modelFactory.metadata
                    .get(['entityDefs', entityType, 'collection', 'order']);

                let className = this.modelFactory.metadata
                    .get(['clientDefs', entityType, 'collection']) || 'collection';

                let defs = this.metadata.get(['entityDefs', entityType]) || {};

                Espo.loader.require(className, Collection => {
                    let collection = new Collection(null, {
                        entityType: entityType,
                        orderBy: orderBy,
                        order: order,
                        defs: defs,
                    });

                    collection.model = Model;
                    collection.entityType = entityType;
                    collection.maxMaxSize = this.recordListMaxSizeLimit;

                    if (callback) {
                        callback.call(context, collection);
                    }

                    resolve(collection);
                });
            });
        });
    }
}

export default CollectionFactory;
PK]�Ψ$��	loader.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

(function () {

    const root = this;

    if (!root.Espo) {
        root.Espo = {};
    }

    if (root.Espo.loader) {
        throw new Error("Loader was already loaded.");
    }

    /**
     * A callback with resolved dependencies passed as parameters.
     * Should return a value to define a module.
     *
     * @callback Loader~requireCallback
     * @param {...any} arguments Resolved dependencies.
     * @returns {*}
     */

    /**
     * @typedef {Object} Loader~libData
     * @property {string} [exportsTo] Exports to.
     * @property {string} [exportsAs] Exports as.
     * @property {boolean} [sourceMap] Has a source map.
     * @property {string} [exposeAs] To expose to global as.
     * @property {string} [path] A path.
     * @property {string} [devPath] A path in developer mode.
     */

    /**
     * @typedef {Object} Loader~dto
     * @property {string} path
     * @property {function(value): void} callback
     * @property {function|null} [errorCallback]
     * @property {'script'|'text'} dataType
     * @property {string} id
     * @property {'amd'|'lib'|'res'} type
     * @property {string|null} exportsTo
     * @property {string|null} exportsAs
     * @property {string} [url]
     * @property {boolean} [useCache]
     * @property {boolean} [suppressAmd]
     */

    /**
     * A loader. Used for loading and defining AMD modules, resource loading.
     * Handles caching.
     */
    class Loader {

        /**
         * @param {int|null} [_cacheTimestamp=null]
         */
        constructor(_cacheTimestamp) {
            this._cacheTimestamp = _cacheTimestamp || null;
            /** @type {Object.<string, Loader~libData>} */
            this._libsConfig = {};
            this._loadCallbacks = {};
            this._pathsBeingLoaded = {};
            this._dataLoaded = {};
            this._definedMap = {};
            this._aliasMap = {};
            this._contextId = null;
            this._responseCache = null;
            this._basePath = '';

            this._internalModuleList = [];
            this._transpiledModuleList = [];
            this._internalModuleMap = {};
            this._isDeveloperMode = false;

            let baseUrl = window.location.origin + window.location.pathname;

            if (baseUrl.slice(-1) !== '/') {
                baseUrl = window.location.pathname.includes('.') ?
                    baseUrl.slice(0, baseUrl.lastIndexOf('/')) + '/' :
                    baseUrl + '/';
            }

            this._baseUrl = baseUrl;

            this._isDeveloperModeIsSet = false;
            this._basePathIsSet = false;
            this._responseCacheIsSet = false;
            this._internalModuleListIsSet = false;
            this._bundleFileMap = {};
            this._bundleMapping = {};
            /** @type {Object.<string, string[]>} */
            this._bundleDependenciesMap = {};
            /** @type {Object.<string, Promise>} */
            this._bundlePromiseMap = {};

            this._addLibsConfigCallCount = 0;
            this._addLibsConfigCallMaxCount = 2;
        }

        /**
         * @param {boolean} isDeveloperMode
         */
        setIsDeveloperMode(isDeveloperMode) {
            if (this._isDeveloperModeIsSet) {
                throw new Error('Is-Developer-Mode is already set.');
            }

            this._isDeveloperMode = isDeveloperMode;
            this._isDeveloperModeIsSet = true;
        }

        /**
         * @param {string} basePath
         */
        setBasePath(basePath) {
            if (this._basePathIsSet) {
                throw new Error('Base path is already set.');
            }

            this._basePath = basePath;
            this._basePathIsSet = true;
        }

        /**
         * @returns {Number}
         */
        getCacheTimestamp() {
            return this._cacheTimestamp;
        }

        /**
         * @param {Number} cacheTimestamp
         */
        setCacheTimestamp(cacheTimestamp) {
            this._cacheTimestamp = cacheTimestamp;
        }

        /**
         * @param {Cache} responseCache
         */
        setResponseCache(responseCache) {
            if (this._responseCacheIsSet) {
                throw new Error('Response-Cache is already set');
            }

            this._responseCache = responseCache;
            this._responseCacheIsSet = true;
        }

        /**
         * @param {string[]} internalModuleList
         */
        setInternalModuleList(internalModuleList) {
            if (this._internalModuleListIsSet) {
                throw new Error('Internal-module-list is already set');
            }

            this._internalModuleList = internalModuleList;
            this._internalModuleMap = {};
            this._internalModuleListIsSet = true;
        }

        /**
         * @param {string[]} transpiledModuleList
         */
        setTranspiledModuleList(transpiledModuleList) {
            this._transpiledModuleList = transpiledModuleList;
        }

        /**
         * @private
         * @param {string} id
         */
        _get(id) {
            if (id in this._definedMap) {
                return this._definedMap[id];
            }

            return void 0;
        }

        /**
         * @private
         * @param {string} id
         * @param {*} value
         */
        _set(id, value) {
            this._definedMap[id] = value;

            if (id.slice(0, 4) === 'lib!') {
                const libName = id.slice(4);

                const libsData = this._libsConfig[libName];

                if (libsData && libsData.exposeAs) {
                    let key = libsData.exposeAs;

                    window[key] = value;
                }
            }
        }

        /**
         * @private
         * @param {string} id
         * @return {string}
         */
        _idToPath(id) {
            if (id.indexOf(':') === -1) {
                return 'client/lib/transpiled/src/' + id + '.js';
            }

            let [mod, namePart] = id.split(':');

            if (mod === 'custom') {
                return 'client/custom/src/' + namePart + '.js';
            }

            const transpiled = this._transpiledModuleList.includes(mod);
            const internal = this._isModuleInternal(mod);

            if (transpiled) {
                if (internal) {
                    return `client/lib/transpiled/modules/${mod}/src/${namePart}.js`;
                }

                return `client/custom/modules/${mod}/lib/transpiled/src/${namePart}.js`;
            }

            if (internal) {
                return 'client/modules/' + mod + '/src/' + namePart + '.js';
            }

            return 'client/custom/modules/' + mod + '/src/' + namePart + '.js';
        }

        /**
         * @private
         * @param {string} script
         * @param {string} id
         * @param {string} path
         */
        _execute(script, id, path) {
            /** @var {?string} */
            let module = null;

            const colonIndex = id.indexOf(':');

            if (colonIndex > 0) {
                module = id.substring(0, colonIndex);
            }

            let noStrictMode = false;

            if (!module && id.indexOf('lib!') === 0) {
                noStrictMode = true;

                const realName = id.substring(4);

                const libsData = this._libsConfig[realName] || {};

                if (!this._isDeveloperMode) {
                    if (libsData.sourceMap) {
                        const realPath = path.split('?')[0];

                        script += `\n//# sourceMappingURL=${this._baseUrl + realPath}.map`;
                    }
                }

                if (libsData.exportsTo === 'window' && libsData.exportsAs) {
                    script += `\nwindow.${libsData.exportsAs} = ` +
                        `window.${libsData.exportsAs} || ${libsData.exportsAs}\n`;
                }
            }

            script += `\n//# sourceURL=${this._baseUrl + path}`;

            // For bc.
            if (module && module !== 'crm') {
                noStrictMode = true;
            }

            if (noStrictMode) {
                (new Function(script)).call(root);

                return;
            }

            (new Function("'use strict'; " + script))();
        }

        /**
         * @private
         * @param {string} id
         * @param {*} value
         */
        _executeLoadCallback(id, value) {
            if (!(id in this._loadCallbacks)) {
                return;
            }

            this._loadCallbacks[id].forEach(callback => callback(value));

            delete this._loadCallbacks[id];
        }

        /**
         * Define a module.
         *
         * @param {string|null} id A module name to be defined.
         * @param {string[]} dependencyIds A dependency list.
         * @param {Loader~requireCallback} callback A callback with resolved dependencies
         *   passed as parameters. Should return a value to define the module.
         */
        define(id, dependencyIds, callback) {
            if (id) {
                id = this._normalizeId(id);
            }

            if (this._contextId) {
                id = id || this._contextId;

                this._contextId = null;
            }

            let existing = this._get(id);

            if (typeof existing !== 'undefined') {
                return;
            }

            if (!dependencyIds) {
                this._defineProceed(callback, id, [], -1);

                return;
            }

            let indexOfExports = dependencyIds.indexOf('exports');

            if (Array.isArray(dependencyIds)) {
                dependencyIds = dependencyIds.map(depId => this._normalizeIdPath(depId, id));
            }

            this.require(dependencyIds, (...args) => {
                this._defineProceed(callback, id, args, indexOfExports);
            });
        }

        /**
         * @private
         * @param {function} callback
         * @param {string} id
         * @param {Array} args
         * @param {number} indexOfExports
         */
        _defineProceed(callback, id, args, indexOfExports) {
            let value = callback.apply(root, args);

            if (typeof value === 'undefined' && indexOfExports === -1 && id) {
                throw new Error(`Could not load '${id}'.`);
            }

            if (indexOfExports !== -1) {
                let exports =  args[indexOfExports];

                // noinspection JSUnresolvedReference
                value = ('default' in exports) ? exports.default : exports;
            }

            if (!id) {
                console.error(value);
                // Libs can define w/o id and set to the root.
                // Not supposed to happen as should be suppressed by require.amd = false;
                return;
            }

            this._set(id, value);
            this._executeLoadCallback(id, value);
        }

        /**
         * Require a module or multiple modules.
         *
         * @param {string|string[]} id A module or modules to require.
         * @param {Loader~requireCallback} callback A callback with resolved dependencies.
         * @param {Function|null} [errorCallback] An error callback.
         */
        require(id, callback, errorCallback) {
            let list;

            if (Object.prototype.toString.call(id) === '[object Array]') {
                list = id;

                list.forEach((item, i) => {
                    list[i] = this._normalizeId(item);
                });
            }
            else if (id) {
                id = this._normalizeId(id);

                list = [id];
            }
            else {
                list = [];
            }

            let totalCount = list.length;

            if (totalCount === 1) {
                this._load(list[0], callback, errorCallback);

                return;
            }

            if (totalCount) {
                let readyCount = 0;
                const loaded = {};

                list.forEach(depId => {
                    this._load(depId, c => {
                        loaded[depId] = c;

                        readyCount++;

                        if (readyCount === totalCount) {
                            let args = [];

                            for (let i in list) {
                                args.push(loaded[list[i]]);
                            }

                            callback.apply(root, args);
                        }
                    });
                });

                return;
            }

            callback.apply(root);
        }

        /**
         * @private
         */
        _convertCamelCaseToHyphen(string) {
            if (string === null) {
                return string;
            }

            return string.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
        }

        /**
         * @param {string} id
         * @param {string} subjectId
         * @private
         */
        _normalizeIdPath(id, subjectId) {
            if (id.charAt(0) !== '.') {
                return id;
            }

            if (id.slice(0, 2) !== './' && id.slice(0, 3) !== '../') {
                return id;
            }

            let outputPath = id;

            const dirParts = subjectId.split('/').slice(0, -1);

            if (id.slice(0, 2) === './') {
                outputPath = dirParts.join('/') + '/' + id.slice(2);
            }

            const parts = outputPath.split('/');

            let up = 0;

            for (const part of parts) {
                if (part === '..') {
                    up++;

                    continue;
                }

                break;
            }

            if (!up) {
                return outputPath;
            }

            if (up) {
                outputPath = dirParts.slice(0, -up).join('/') + '/' + outputPath.slice(3 * up);
            }

            return outputPath;
        }

        /**
         * @private
         * @param {string} id
         * @return {string}
         */
        _restoreId(id) {
            if (!id.includes(':')) {
                return id;
            }

            const [mod, part] = id.split(':');

            return `modules/${mod}/${part}`;
        }

        /**
         * @private
         * @param {string} id
         * @return {string}
         */
        _normalizeId(id) {
            if (id in this._aliasMap) {
                id = this._aliasMap[id];
            }

            if (~id.indexOf('.') && !~id.indexOf('!') && id.slice(-3) !== '.js') {
                console.warn(`${id}: module ID should use slashes instead of dots and hyphen instead of CamelCase.`);
            }

            if (!!/[A-Z]/.exec(id[0])) {
                if (id.indexOf(':') !== -1) {
                    const arr = id.split(':');
                    const modulePart = arr[0];
                    const namePart = arr[1];

                    return this._convertCamelCaseToHyphen(modulePart) + ':' +
                        this._convertCamelCaseToHyphen(namePart)
                            .split('.')
                            .join('/');
                }

                return this._convertCamelCaseToHyphen(id).split('.').join('/');
            }

            if (id.startsWith('modules/')) {
                id = id.slice(8);

                const index = id.indexOf('/');

                if (index > 0) {
                    const mod = id.slice(0, index);
                    id = id.slice(index + 1);

                    return mod + ':' + id;
                }
            }

            return id;
        }

        /**
         * @private
         * @param {string} id
         * @param {function(*)} callback
         */
        _addLoadCallback(id, callback) {
            if (!(id in this._loadCallbacks)) {
                this._loadCallbacks[id] = [];
            }

            this._loadCallbacks[id].push(callback);
        }

        /**
         * @private
         * @param {string} id
         * @param {function(*)} callback
         * @param {function()} [errorCallback]
         */
        _load(id, callback, errorCallback) {
            if (id === 'exports') {
                callback({});

                return;
            }

            let dataType, type, path, exportsTo, exportsAs;

            let realName = id;
            let suppressAmd = false;

            if (id.indexOf('lib!') === 0) {
                dataType = 'script';
                type = 'lib';

                realName = id.slice(4);
                path = realName;

                exportsTo = 'window';
                exportsAs = null;

                let isDefinedLib = realName in this._libsConfig;

                if (isDefinedLib) {
                    const libData = this._libsConfig[realName] || {};

                    path = libData.path || path;

                    if (this._isDeveloperMode && libData.devPath) {
                        path = libData.devPath;
                    }

                    exportsTo = libData.exportsTo || null;
                    exportsAs = libData.exportsAs || null;
                }

                if (isDefinedLib && !exportsTo) {
                    type = 'amd';
                }

                if (!isDefinedLib && id.slice(-3) === '.js') {
                    suppressAmd = true;
                }

                if (exportsAs) {
                    suppressAmd = true;
                }

                if (path.indexOf(':') !== -1) {
                    console.error(`Not allowed path '${path}'.`);

                    throw new Error();
                }

                let obj = void 0;

                if (exportsTo && exportsAs) {
                    obj = this._fetchObject(exportsTo, exportsAs);
                }

                if (typeof obj === 'undefined' && id in this._definedMap) {
                    obj = this._definedMap[id];
                }

                if (typeof obj !== 'undefined') {
                    callback(obj);

                    return;
                }
            }
            else if (id.indexOf('res!') === 0) {
                dataType = 'text';
                type = 'res';

                realName = id.slice(4);
                path = realName;

                if (path.indexOf(':') !== -1) {
                    console.error(`Not allowed path '${path}'.`);

                    throw new Error();
                }
            }
            else {
                dataType = 'script';
                type = 'amd';

                if (!id || id === '') {
                    throw new Error("Can't load with empty module ID.");
                }

                const value = this._get(id);

                if (typeof value !== 'undefined') {
                    callback(value);

                    return;
                }

                const restoredId = this._restoreId(id);

                if (restoredId in this._bundleMapping) {
                    let bundleName = this._bundleMapping[restoredId];

                    this._requireBundle(bundleName).then(() => {
                        let value = this._get(id);

                        if (typeof value === 'undefined') {
                            let msg = `Could not obtain module '${restoredId}' from bundle '${bundleName}'.`;
                            console.error(msg);

                            throw new Error(msg);
                        }

                        callback(value);
                    });

                    return;
                }

                path = this._idToPath(id);
            }

            if (id in this._dataLoaded) {
                callback(this._dataLoaded[id]);

                return;
            }

            /** @type {Loader~dto} */
            const dto = {
                id: id,
                type: type,
                dataType: dataType,
                path: path,
                callback: callback,
                errorCallback: errorCallback,
                exportsAs: exportsAs,
                exportsTo: exportsTo,
                suppressAmd: suppressAmd,
            };

            if (path in this._pathsBeingLoaded) {
                this._addLoadCallback(id, callback);

                return;
            }

            this._pathsBeingLoaded[path] = true;

            let useCache = false;

            if (this._cacheTimestamp) {
                useCache = true;

                const sep = (path.indexOf('?') > -1) ? '&' : '?';

                path += sep + 'r=' + this._cacheTimestamp;
            }

            const url = this._basePath + path;

            dto.path = path;
            dto.url = url;
            dto.useCache = useCache;

            if (!this._responseCache) {
                this._processRequest(dto);

                return;
            }

            this._responseCache
                .match(new Request(url))
                .then(response => {
                    if (!response) {
                        this._processRequest(dto);

                        return;
                    }

                    response.text()
                        .then(text => this._handleResponseText(dto, text));
                });
        }

        /**
         * @private
         * @param {string} name
         * @return {Promise}
         */
        _requireBundle(name) {
            if (this._bundlePromiseMap[name]) {
                return this._bundlePromiseMap[name];
            }

            const dependencies = this._bundleDependenciesMap[name] || [];

            if (!dependencies.length) {
                this._bundlePromiseMap[name] = this._addBundle(name);

                return this._bundlePromiseMap[name];
            }

            this._bundlePromiseMap[name] = new Promise(resolve => {
                let list = dependencies.map(item => {
                    if (item.indexOf('bundle!') === 0) {
                        return this._requireBundle(item.substring(7));
                    }

                    return Espo.loader.requirePromise(item);
                });

                Promise.all(list)
                    .then(() => this._addBundle(name))
                    .then(() => resolve());
            });

            return this._bundlePromiseMap[name];
        }

        /**
         * @private
         * @param {string} name
         * @return {Promise}
         */
        _addBundle(name) {
            let src = this._bundleFileMap[name];

            if (!src) {
                throw new Error(`Unknown bundle '${name}'.`);
            }

            if (this._cacheTimestamp) {
                let sep = (src.indexOf('?') > -1) ? '&' : '?';

                src += sep + 'r=' + this._cacheTimestamp;
            }

            src = this._basePath + src;

            const scriptEl = document.createElement('script');

            scriptEl.setAttribute('type', 'text/javascript')
            scriptEl.setAttribute('src', src);

            scriptEl.addEventListener('error', event => {
                console.error(`Could not load bundle '${name}'.`, event);
            });

            return new Promise(resolve => {
                document.head.appendChild(scriptEl);

                scriptEl.addEventListener('load', () => resolve());
            });
        }

        /**
         * @private
         * @return {*}
         */
        _fetchObject(exportsTo, exportsAs) {
            let from = root;

            if (exportsTo === 'window') {
                from = root;
            }
            else {
                for (const item of exportsTo.split('.')) {
                    from = from[item];

                    if (typeof from === 'undefined') {
                        return void 0;
                    }
                }
            }

            if (exportsAs in from) {
                return from[exportsAs];
            }

            return void 0;
        }

        /**
         * @private
         * @param {Loader~dto} dto
         */
        _processRequest(dto) {
            const url = dto.url;
            const errorCallback = dto.errorCallback;
            const path = dto.path;
            const useCache = dto.useCache;

            const urlObj = new URL(this._baseUrl + url);

            if (!useCache) {
                urlObj.searchParams.append('_', Date.now().toString())
            }

            fetch(urlObj)
                .then(response => {
                    if (!response.ok) {
                        if (typeof errorCallback === 'function') {
                            errorCallback();

                            return;
                        }

                        throw new Error(`Could not fetch asset '${path}'.`);
                    }

                    response.text().then(text => {
                        if (this._responseCache) {
                            this._responseCache.put(url, new Response(text));
                        }

                        this._handleResponseText(dto, text);
                    });
                })
                .catch(() => {
                    if (typeof errorCallback === 'function') {
                        errorCallback();

                        return;
                    }

                    throw new Error(`Could not fetch asset '${path}'.`);
                });
        }

        /**
         * @private
         * @param {Loader~dto} dto
         * @param {string} text
         */
        _handleResponseText(dto, text) {
            const id = dto.id;
            const callback = dto.callback;
            const type = dto.type;
            const dataType = dto.dataType;
            const exportsAs = dto.exportsAs;
            const exportsTo = dto.exportsTo;
            const suppressAmd = dto.suppressAmd;

            this._addLoadCallback(id, callback);

            if (type === 'amd') {
                this._contextId = id;
            }

            if (suppressAmd) {
                define.amd = false;
            }

            if (dataType === 'script') {
                this._execute(text, id, dto.path);
            }

            if (suppressAmd) {
                define.amd = true;
            }

            let value;

            if (type === 'amd') {
                value = this._get(id);

                if (typeof value !== 'undefined') {
                    this._executeLoadCallback(id, value);
                }

                return;
            }

            value = text;

            if (exportsTo && exportsAs) {
                value = this._fetchObject(exportsTo, exportsAs);
            }

            this._dataLoaded[id] = value;

            this._executeLoadCallback(id, value);
        }

        /**
         * @param {Object.<string, Loader~libData>} data
         * @internal
         */
        addLibsConfig(data) {
            if (this._addLibsConfigCallCount === this._addLibsConfigCallMaxCount) {
                throw new Error("Not allowed to call addLibsConfig.");
            }

            this._addLibsConfigCallCount++;

            this._libsConfig = {...this._libsConfig, ...data};
        }

        /**
         * @param {Object.<string, string>} map
         */
        setAliasMap(map) {
            this._aliasMap = map;
        }

        /**
         * @private
         */
        _isModuleInternal(moduleName) {
            if (!(moduleName in this._internalModuleMap)) {
                this._internalModuleMap[moduleName] = this._internalModuleList.indexOf(moduleName) !== -1;
            }

            return this._internalModuleMap[moduleName];
        }

        /**
         * @param {string} name A bundle name.
         * @param {string} file A bundle file.
         * @internal
         */
        mapBundleFile(name, file) {
            this._bundleFileMap[name] = file;
        }

        /**
         * @param {string} name A bundle name.
         * @param {string[]} list Dependencies.
         * @internal
         */
        mapBundleDependencies(name, list) {
            this._bundleDependenciesMap[name] = list;
        }

        /**
         * @param {Object.<string, string>} mapping
         * @internal
         */
        addBundleMapping(mapping) {
            Object.assign(this._bundleMapping, mapping);
        }

        /**
         * @param {string} id
         * @internal
         */
        setContextId(id) {
            this._contextId = id;
        }

        /**
         * Require a module.
         *
         * @param {string} id A module to require.
         * @returns {Promise<*>}
         */
        requirePromise(id) {
            return new Promise((resolve, reject) => {
                this.require(
                    id,
                    arg => resolve(arg),
                    () => reject()
                );
            });
        }
    }

    let loader = new Loader();

    // noinspection JSUnusedGlobalSymbols

    Espo.loader = {

        /**
         * @param {boolean} isDeveloperMode
         * @internal
         */
        setIsDeveloperMode: function (isDeveloperMode) {
            loader.setIsDeveloperMode(isDeveloperMode);
        },

        /**
         * @param {string} basePath
         * @internal
         */
        setBasePath: function (basePath) {
            loader.setBasePath(basePath);
        },

        /**
         * @returns {Number}
         */
        getCacheTimestamp: function () {
            return loader.getCacheTimestamp();
        },

        /**
         * @param {Number} cacheTimestamp
         * @internal
         */
        setCacheTimestamp: function (cacheTimestamp) {
            loader.setCacheTimestamp(cacheTimestamp);
        },

        /**
         * @param {Cache} responseCache
         * @internal
         */
        setResponseCache: function (responseCache) {
            loader.setResponseCache(responseCache);
        },

        /**
         * Define a module.
         *
         * @param {string} id A module name to be defined.
         * @param {string[]} dependencyIds A dependency list.
         * @param {Loader~requireCallback} callback A callback with resolved dependencies
         *   passed as parameters. Should return a value to define the module.
         */
        define: function (id, dependencyIds, callback) {
            loader.define(id, dependencyIds, callback);
        },

        /**
         * Require a module or multiple modules.
         *
         * @param {string|string[]} id A module or modules to require.
         * @param {Loader~requireCallback} callback A callback with resolved dependencies.
         * @param {Function|null} [errorCallback] An error callback.
         */
        require: function (id, callback, errorCallback) {
            loader.require(id, callback, errorCallback);
        },

        /**
         * Require a module or multiple modules.
         *
         * @param {string|string[]} id A module or modules to require.
         * @returns {Promise<unknown>}
         */
        requirePromise: function (id) {
            return loader.requirePromise(id);
        },

        /**
         * @param {Object.<string, Loader~libData>} data
         * @internal
         */
        addLibsConfig: function (data) {
            loader.addLibsConfig(data);
        },

        /**
         * @param {string} name A bundle name.
         * @param {string} file A bundle file.
         * @internal
         */
        mapBundleFile: function (name, file) {
            loader.mapBundleFile(name, file);
        },

        /**
         * @param {string} name A bundle name.
         * @param {string[]} list Dependencies.
         * @internal
         */
        mapBundleDependencies: function (name, list) {
            loader.mapBundleDependencies(name, list);
        },

        /**
         * @param {Object.<string, string>} mapping
         * @internal
         */
        addBundleMapping: function (mapping) {
            loader.addBundleMapping(mapping);
        },

        /**
         * @param {string} id
         * @internal
         */
        setContextId: function (id) {
            loader.setContextId(id);
        },
    };

    /**
     * Require a module or multiple modules.
     *
     * @param {string|string[]} id A module or modules to require.
     * @param {Loader~requireCallback} callback A callback with resolved dependencies.
     * @param {Object} [context] A context.
     * @param {Function|null} [errorCallback] An error callback.
     *
     * @deprecated Use `Espo.loader.require` instead.
     */
    root.require = Espo.require = function (id, callback, context, errorCallback) {
        if (context) {
            callback = callback.bind(context);
        }

        loader.require(id, callback, errorCallback);
    };

    /**
     * Define an [AMD](https://github.com/amdjs/amdjs-api/blob/master/AMD.md) module.
     *
     * 3 signatures:
     * 1. `(callback)` – Unnamed, no dependencies.
     * 2. `(dependencyList, callback)` – Unnamed, with dependencies.
     * 3. `(moduleName, dependencyList, callback)` – Named.
     *
     * @param {string|string[]|Loader~requireCallback} arg1 A module name to be defined,
     *   a dependency list or a callback.
     * @param {string[]|Loader~requireCallback} [arg2] A dependency list or a callback with resolved
     *   dependencies.
     * @param {Loader~requireCallback} [arg3] A callback with resolved dependencies.
     */
    root.define = Espo.define = function (arg1, arg2, arg3) {
        let id = null;
        let depIds = null;
        let callback;

        if (typeof arg1 === 'function') {
            callback = arg1;
        }
        else if (typeof arg1 !== 'undefined' && typeof arg2 === 'function') {
            if (Array.isArray(arg1)) {
                depIds = arg1;
            } else {
                id = arg1;
                depIds = [];
            }

            callback = arg2;
        }
        else {
            id = arg1;
            depIds = arg2;
            callback = arg3;
        }

        loader.define(id, depIds, callback);
    };

    root.define.amd = true;

    (() => {
        const loaderParamsTag = document.querySelector('script[data-name="loader-params"]');

        if (!loaderParamsTag) {
            return;
        }

        /**
         * @type {{
         *     cacheTimestamp?: int,
         *     basePath?: string,
         *     internalModuleList?: [],
         *     transpiledModuleList?: [],
         *     libsConfig?: Object.<string, Loader~libData>,
         *     aliasMap?: Object.<string, *>,
         * }}
         */
        const params = JSON.parse(loaderParamsTag.textContent);

        loader.setCacheTimestamp(params.cacheTimestamp);
        loader.setBasePath(params.basePath);
        loader.setInternalModuleList(params.internalModuleList);
        loader.setTranspiledModuleList(params.transpiledModuleList);
        loader.addLibsConfig(params.libsConfig);
        loader.setAliasMap(params.aliasMap);
    })();

}).call(window);
PK]pϩc�cmodel.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module model */

import {Events, View as BullView} from 'bullbone';
import _ from 'underscore';

/**
 * When attributes have changed.
 *
 * @event Model#change
 * @param {Model} model A model.
 * @param {Object.<string, *>} o Options.
 */

/**
 * On sync with backend.
 *
 * @event Model#sync
 * @param {Model} model A model.
 * @param {Object} response Response from backend.
 * @param {Object.<string, *>} o Options.
 */

/**
 * Defs.
 *
 * @typedef module:model~defs
 * @type {Object}
 * @property {Object.<string, Object.<string, *>>} [fields] Fields.
 * @property {Object.<string, Object.<string, *>>} [links] Links.
 */

/**
 * A model.
 *
 * @mixes Bull.Events
 */
class Model {

    /**
     * A root URL. An ID will be appended. Used for syncing with backend.
     *
     * @type {string|null}
     */
    urlRoot = null

    /**
     * A URL. If not empty, then will be used for syncing instead of `urlRoot`.
     *
     * @type {string|null}
     */
    url = null

    /**
     * A name.
     *
     * @type {string|null}
     */
    name = null

    /**
     * An entity type.
     *
     * @type {string|null}
     */
    entityType = null

    /**
     * A last request promise.
     *
     * @type {module:ajax.AjaxPromise|null}
     */
    lastSyncPromise = null

    /** @private */
    _pending
    /** @private */
    _changing

    /**
     * @param {Object.<string, *>|Model} [attributes]
     * @param {{
     *     collection?: module:collection,
     *     entityType?: string,
     *     urlRoot?: string,
     *     url?: string,
     *     defs?: module:model~defs,
     *     user?: module:models/user,
     *     dateTime?: module:date-time,
     * }} [options]
     */
    constructor(attributes, options) {
        options = options || {};

        /**
         * An ID attribute.
         * @type {string}
         */
        this.idAttribute = 'id';

        /**
         * A record ID.
         * @type {string|null}
         */
        this.id = null;

        /**
         * An instance ID.
         * @type {string}
         */
        this.cid = _.uniqueId('c');

        /**
         * Attribute values.
         * @type {Object.<string, *>}
         */
        this.attributes = {};

        if (options.collection) {
            this.collection = options.collection;
        }

        this.set(attributes || {});

        /**
         * Definitions.
         */
        this.defs = options.defs || {};

        if (!this.defs.fields) {
            this.defs.fields = {};
        }

        if (options.entityType) {
            this.entityType = options.entityType;
            this.name = options.entityType;
            this.urlRoot = options.entityType;
        }

        this.urlRoot = options.urlRoot || this.urlRoot;
        this.url = options.url || this.url;

        /** @private */
        this.dateTime = options.dateTime || null;

        /** @private */
        this.changed = {};
        /** @private */
        this._previousAttributes = null;
    }

    /**
     * @protected
     * @param {string} [method] HTTP method.
     * @param {Model} model
     * @param {Object.<string, *>} [options]
     * @returns {module:ajax.AjaxPromise|Promise}
     */
    sync(method, model, options) {
        const methodMap = {
            'create': 'POST',
            'update': 'PUT',
            'patch': 'PUT',
            'delete': 'DELETE',
            'read': 'GET',
        };

        let httpMethod = methodMap[method];

        if (!httpMethod) {
            throw new Error(`Bad request method '${method}'.`);
        }

        options = options || {};

        let url = this.composeSyncUrl();

        if (!url) {
            throw new Error(`No 'url'.`);
        }

        const data = model && ['create', 'update', 'patch'].includes(method) ?
            (options.attributes || model.getClonedAttributes()) : null;

        let error = options.error;

        options.error = (xhr, textStatus, errorThrown) => {
            options.textStatus = textStatus;
            options.errorThrown = errorThrown;

            if (error) {
                error.call(options.context, xhr, textStatus, errorThrown);
            }
        };

        let stringData = data ? JSON.stringify(data) : null;

        const ajaxPromise = !options.bypassRequest ?
            Espo.Ajax.request(url, httpMethod, stringData, options) :
            Promise.resolve();

        options.xhr = ajaxPromise.xhr;

        model.trigger('request', url, httpMethod, data, ajaxPromise, options);

        return ajaxPromise;
    }

    /**
     * Set an attribute value.
     *
     * @param {(string|Object)} attribute An attribute name or a {key => value} object.
     * @param {*} [value] A value or options if the first argument is an object.
     * @param {{silent?: boolean} & Object.<string, *>} [options] Options. `silent` won't trigger a `change` event.
     * @returns {this}
     * @fires Model#change Unless `{silent: true}`.
     */
    set(attribute, value, options) {
        if (attribute == null) {
            return this;
        }

        let attributes;

        if (typeof attribute === 'object') {
            return this.setMultiple(attribute, value);
        }

        attributes = {};
        attributes[attribute] = value;

        return this.setMultiple(attributes, options);
    }

    /**
     * Set attributes values.
     *
     * @param {Object.<string, *>} attributes
     * @param {{
     *     silent?: boolean,
     *     unset?: boolean,
     * } & Object.<string, *>} [options] Options. `silent` won't trigger a `change` event.
     * @return {this}
     * @fires Model#change Unless `{silent: true}`.
     * @copyright Credits to Backbone.js.
     */
    setMultiple(attributes, options) {
        if (this.idAttribute in attributes) {
            this.id = attributes[this.idAttribute];
        }

        options = options || {};

        let changes = [];
        let changing = this._changing;

        this._changing = true;

        if (!changing) {
            this._previousAttributes = _.clone(this.attributes);
            this.changed = {};
        }

        let current = this.attributes;
        let changed = this.changed;
        let previous = this._previousAttributes;

        for (let attribute in attributes) {
            let value = attributes[attribute];

            if (!_.isEqual(current[attribute], value)) {
                changes.push(attribute);
            }

            if (!_.isEqual(previous[attribute], value)) {
                changed[attribute] = value;
            } else {
                delete changed[attribute];
            }

            options.unset ?
                delete current[attribute] :
                current[attribute] = value;
        }

        if (!options.silent) {
            if (changes.length) {
                this._pending = options;
            }

            for (let i = 0; i < changes.length; i++) {
                this.trigger('change:' + changes[i], this, current[changes[i]], options);
            }
        }

        if (changing) {
            return this;
        }

        if (!options.silent) {
            // Changes can be recursively nested within `change` events.
            while (this._pending) {
                options = this._pending;
                this._pending = false;

                this.trigger('change', this, options);
            }
        }

        this._pending = false;
        this._changing = false;

        return this;
    }

    /**
     * Unset an attribute.
     *
     * @param {string} attribute An attribute.
     * @param {{silent?: boolean} & Object.<string, *>} [options] Options.
     * @return {Model}
     */
    unset(attribute, options) {
        options = {...options, unset: true};

        let attributes = {};
        attributes[attribute] = null;

        return this.setMultiple(attributes, options);
    }

    /**
     * Get an attribute value.
     *
     * @param {string} attribute An attribute name.
     * @returns {*}
     */
    get(attribute) {
        if (attribute === this.idAttribute && this.id) {
            return this.id;
        }

        return this.attributes[attribute];
    }

    /**
     * Whether attribute is set.
     *
     * @param {string} attribute An attribute name.
     * @returns {boolean}
     */
    has(attribute) {
        let value = this.get(attribute);

        return typeof value !== 'undefined';
    }

    /**
     * Removes all attributes from the model.
     * Fires a `change` event unless `silent` is passed as an option.
     *
     * @param {{silent?: boolean} & Object.<string, *>} [options] Options.
     */
    clear(options) {
        let attributes = {};

        for (let key in this.attributes) {
            attributes[key] = void 0;
        }

        options = {...options, unset: true};

        return this.set(attributes, options);
    }

    /**
     * Whether is new.
     *
     * @returns {boolean}
     */
    isNew() {
        return !this.id;
    }

    /**
     * Whether an attribute changed. To be called only within a 'change' event handler.
     *
     * @param {string} [attribute]
     * @return {boolean}
     */
    hasChanged(attribute) {
        if (!attribute) {
            return !_.isEmpty(this.changed);
        }

        return _.has(this.changed, attribute);
    }

    /**
     * Get changed attribute values. To be called only within a 'change' event handler.
     *
     * @return {Object.<string, *>}
     */
    changedAttributes() {
        return this.hasChanged() ? _.clone(this.changed) : {};
    }

    /**
     * Get previous attributes. To be called only within a 'change' event handler.
     *
     * @return {Object.<string, *>}
     */
    previousAttributes() {
        return _.clone(this._previousAttributes);
    }

    /**
     * Get a previous attribute value. To be called only within a 'change' event handler.
     *
     * @param attribute
     * @return {*}
     */
    previous(attribute) {
        if (!this._previousAttributes) {
            return null;
        }

        return this._previousAttributes[attribute];
    }

    /**
     * Fetch values from the backend.
     *
     * @param {Object.<string, *>} [options] Options.
     * @returns {Promise}
     * @fires Model#sync
     */
    fetch(options) {
        options = {...options};

        let success = options.success;

        options.success = response => {
            let serverAttributes = this.prepareAttributes(response, options);

            this.set(serverAttributes, options);

            if (success) {
                success.call(options.context, this, response, options);
            }

            this.trigger('sync', this, response, options);
        };

        this.lastSyncPromise = this.sync('read', this, options);

        return this.lastSyncPromise;
    }

    /**
     * Save values to the backend.
     *
     * @param {Object.<string, *>} [attributes] Attribute values.
     * @param {{
     *     patch?: boolean,
     *     wait?: boolean,
     * } & Object.<string, *>} [options] Options.
     * @returns {Promise<Object.<string, *>>}
     * @fires Model#sync
     * @copyright Credits to Backbone.js.
     */
    save(attributes, options) {
        options = {...options};

        if (attributes && !options.wait) {
            this.setMultiple(attributes, options);
        }

        const success = options.success;

        const setAttributes = this.attributes;

        options.success = response => {
            this.attributes = setAttributes;

            let responseAttributes = this.prepareAttributes(response, options);

            if (options.wait) {
                responseAttributes = {...setAttributes, ...responseAttributes};
            }

            if (responseAttributes) {
                this.setMultiple(responseAttributes, options);
            }

            if (success) {
                success.call(options.context, this, response, options);
            }

            this.trigger('sync', this, response, options);
        };

        const error = options.error;

        options.error = response => {
            if (error) {
                error.call(options.context, this, response, options);
            }

            this.trigger('error', this, response, options);
        };

        if (attributes && options.wait) {
            // Set temporary attributes to properly find new IDs.
            this.attributes =  {...setAttributes, ...attributes};
        }

        let method = this.isNew() ?
            'create' :
            (options.patch ? 'patch' : 'update');

        if (method === 'patch') {
            options.attributes = attributes;
        }

        const result = this.sync(method, this, options);

        this.attributes = setAttributes;

        return result;
    }

    /**
     * Delete the record in the backend.
     *
     * @param {{wait: boolean} & Object.<string, *>} [options] Options.
     * @returns {Promise}
     * @fires Model#sync
     * @copyright Credits to Backbone.js.
     */
    destroy(options) {
        options = _.clone(options || {});

        let success = options.success;

        const destroy = () => {
            this.stopListening();
            this.trigger('destroy', this, this.collection, options);
        };

        options.success = response => {
            if (options.wait) {
                destroy();
            }

            if (success) {
                success.call(options.context, this, response, options);
            }

            if (!this.isNew()) {
                this.trigger('sync', this, response, options);
            }
        };

        if (this.isNew()) {
            _.defer(options.success);

            if (!options.wait) {
                destroy();
            }

            return Promise.resolve();
        }

        let error = options.error;

        options.error = response => {
            if (error) {
                error.call(options.context, this, response, options);
            }

            this.trigger('error', this, response, options);
        };

        let result = this.sync('delete', this, options);

        if (!options.wait) {
            destroy();
        }

        return result;
    }

    /**
     * Compose a URL for syncing.
     *
     * @protected
     * @return {string}
     */
    composeSyncUrl() {
        if (this.url) {
            return this.url;
        }

        let urlRoot = this.urlRoot;

        if (!urlRoot && this.collection) {
            urlRoot = this.collection.urlRoot
        }

        if (!urlRoot) {
            throw new Error("No urlRoot.");
        }

        if (this.isNew()) {
            return urlRoot;
        }

        let id = this.get(this.idAttribute);

        return urlRoot.replace(/[^\/]$/, '$&/') + encodeURIComponent(id);
    }

    // noinspection JSUnusedLocalSymbols
    /**
     * Prepare attributes.
     *
     * @param {*} response A response from the backend.
     * @param {Object.<string, *>} options Options.
     * @return {*} Attributes.
     * @internal
     */
    prepareAttributes(response, options) {
        return response;
    }

    /**
     * Clone.
     *
     * @return {Model}
     */
    clone() {
        return new this.constructor(
            Espo.Utils.cloneDeep(this.attributes),
            {
                entityType: this.entityType,
                urlRoot: this.urlRoot,
                url: this.url,
                defs: this.defs,
                dateTime: this.dateTime,
            }
        );
    }

    /**
     * Set defs.
     *
     * @param {module:model~defs} defs
     */
    setDefs(defs) {
        this.defs = defs || {};

        if (!this.defs.fields) {
            this.defs.fields = {};
        }
    }

    /**
     * Get cloned attribute values.
     *
     * @returns {Object.<string, *>}
     */
    getClonedAttributes() {
        return Espo.Utils.cloneDeep(this.attributes);
    }

    /**
     * Populate default values.
     */
    populateDefaults() {
        let defaultHash = {};

        const fieldDefs = this.defs.fields;

        for (let field in fieldDefs) {
            let defaultValue = this.getFieldParam(field, 'default');

            if (defaultValue !== null) {
                try {
                    defaultValue = this.parseDefaultValue(defaultValue);

                    defaultHash[field] = defaultValue;
                }
                catch (e) {
                    console.error(e);
                }
            }

            let defaultAttributes = this.getFieldParam(field, 'defaultAttributes');

            if (defaultAttributes) {
                for (let attribute in defaultAttributes) {
                    defaultHash[attribute] = defaultAttributes[attribute];
                }
            }
        }

        defaultHash = Espo.Utils.cloneDeep(defaultHash);

        for (let attr in defaultHash) {
            if (this.has(attr)) {
                delete defaultHash[attr];
            }
        }

        this.set(defaultHash, {silent: true});
    }

    /**
     * @protected
     * @param {*} defaultValue
     * @returns {*}
     */
    parseDefaultValue(defaultValue) {
        if (
            typeof defaultValue === 'string' &&
            defaultValue.indexOf('javascript:') === 0
        ) {
            let code = defaultValue.substring(11);

            defaultValue = (new Function( "with(this) { " + code + "}")).call(this);
        }

        return defaultValue;
    }

    /**
     * Get a link multiple column value.
     *
     * @param {string} field
     * @param {string} column
     * @param {string} id
     * @returns {*}
     */
    getLinkMultipleColumn(field, column, id) {
        return ((this.get(field + 'Columns') || {})[id] || {})[column];
    }

    /**
     * Set relate data (when creating a related record).
     *
     * @param {Object} data
     */
    setRelate(data) {
        let setRelate = options => {
            let link = options.link;
            let model = /** @type {module:model} */options.model;

            if (!link || !model) {
                throw new Error('Bad related options');
            }

            let type = this.defs.links[link].type;

            switch (type) {
                case 'belongsToParent':
                    this.set(link + 'Id', model.id);
                    this.set(link + 'Type', model.entityType);
                    this.set(link + 'Name', model.get('name'));

                    break;

                case 'belongsTo':
                    this.set(link + 'Id', model.id);
                    this.set(link + 'Name', model.get('name'));

                    break;

                case 'hasMany':
                    let ids = [];
                    ids.push(model.id);

                    let names = {};

                    names[model.id] = model.get('name');

                    this.set(link + 'Ids', ids);
                    this.set(link + 'Names', names);

                    break;
            }
        };

        if (Object.prototype.toString.call(data) === '[object Array]') {
            data.forEach(options => {
                setRelate(options);
            });

            return;
        }

        setRelate(data);
    }

    /**
     * Get a field type.
     *
     * @param {string} field
     * @returns {string|null}
     */
    getFieldType(field) {
        if (!this.defs || !this.defs.fields) {
            return null;
        }

        if (field in this.defs.fields) {
            return this.defs.fields[field].type || null;
        }

        return null;
    }

    /**
     * Get a field param.
     *
     * @param {string} field
     * @param {string} param
     * @returns {*}
     */
    getFieldParam(field, param) {
        if (!this.defs || !this.defs.fields) {
            return null;
        }

        if (field in this.defs.fields) {
            if (param in this.defs.fields[field]) {
                return this.defs.fields[field][param];
            }
        }

        return null;
    }

    /**
     * Get a link type.
     *
     * @param {string} link
     * @returns {string|null}
     */
    getLinkType(link) {
        if (!this.defs || !this.defs.links) {
            return null;
        }

        if (link in this.defs.links) {
            return this.defs.links[link].type || null;
        }

        return null;
    }

    /**
     * Get a link param.
     *
     * @param {string} link A link.
     * @param {string} param A param.
     * @returns {*}
     */
    getLinkParam(link, param) {
        if (!this.defs || !this.defs.links) {
            return null;
        }

        if (link in this.defs.links) {
            if (param in this.defs.links[link]) {
                return this.defs.links[link][param];
            }
        }

        return null;
    }

    /**
     * Is a field read-only.
     *
     * @param {string} field A field.
     * @returns {bool}
     */
    isFieldReadOnly(field) {
        return this.getFieldParam(field, 'readOnly') || false;
    }

    /**
     * If a field required.
     *
     * @param {string} field A field.
     * @returns {bool}
     */
    isRequired(field) {
        return this.getFieldParam(field, 'required') || false;
    }

    /**
     * Get IDs of a link-multiple field.
     *
     * @param {string} field A link-multiple field name.
     * @returns {string[]}
     */
    getLinkMultipleIdList(field) {
        return this.get(field + 'Ids') || [];
    }

    /**
     * Get team IDs.
     *
     * @returns {string[]}
     */
    getTeamIdList() {
        return this.get('teamsIds') || [];
    }

    /**
     * Whether it has a field.
     *
     * @param {string} field A field.
     * @returns {boolean}
     */
    hasField(field) {
        return ('defs' in this) && ('fields' in this.defs) && (field in this.defs.fields);
    }

    /**
     * Whether has a link.
     *
     * @param {string} link A link.
     * @returns {boolean}
     */
    hasLink(link) {
        return ('defs' in this) && ('links' in this.defs) && (link in this.defs.links);
    }

    /**
     * @returns {boolean}
     */
    isEditable() {
        return true;
    }

    /**
     * @returns {boolean}
     */
    isRemovable() {
        return true;
    }

    /**
     * Get an entity type.
     *
     * @returns {string}
     */
    getEntityType() {
        return this.name;
    }

    /**
     * Abort the last fetch.
     */
    abortLastFetch() {
        if (this.lastSyncPromise && this.lastSyncPromise.getReadyState() < 4) {
            this.lastSyncPromise.abort();
        }
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * @deprecated Use `getClonedAttributes`.
     * @todo Remove in v9.0.
     * @return {Object.<string, *>}
     */
    toJSON() {
        console.warn(`model.toJSON is deprecated. Use 'getClonedAttributes' instead.`);

        return this.getClonedAttributes();
    }
}

Object.assign(Model.prototype, Events);

Model.extend = BullView.extend;

export default Model;
PK]�|bs�"�"acl.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module acl */

import {View as BullView} from 'bullbone';

/**
 * Internal class for access checking. Can be extended to customize access checking
 * for a specific scope.
 */
class Acl {

    /**
     * @param {module:models/user} user A user.
     * @param {string} scope A scope.
     * @param {Object} params Parameters.
     */
    constructor(user, scope, params) {
        /**
         * A user.
         *
         * @type {module:models/user|null}
         * @protected
         */
        this.user = user || null;
        this.scope = scope;

        params = params || {};

        this.aclAllowDeleteCreated = params.aclAllowDeleteCreated;
        this.teamsFieldIsForbidden = params.teamsFieldIsForbidden;
        this.forbiddenFieldList = params.forbiddenFieldList;
    }

    /**
     * Get a user.
     *
     * @returns {module:models/user}
     * @protected
     */
    getUser() {
        return this.user;
    }

    /**
     * Check access to a scope.
     *
     * @param {string|boolean|Object.<string, string>} data Access data.
     * @param {module:acl-manager~action|null} [action=null] An action.
     * @param {boolean} [precise=false] To return `null` if `inTeam == null`.
     * @param {Object|null} [entityAccessData=null] Entity access data. `inTeam`, `isOwner`.
     * @returns {boolean|null} True if access allowed.
     */
    checkScope(data, action, precise, entityAccessData) {
        entityAccessData = entityAccessData || {};

        let inTeam = entityAccessData.inTeam;
        let isOwner = entityAccessData.isOwner;

        if (this.getUser().isAdmin()) {
            if (data === false) {
                return false;
            }

            return true;
        }

        if (data === false) {
            return false;
        }

        if (data === true) {
            return true;
        }

        if (typeof data === 'string') {
            return true;
        }

        if (data === null) {
            return false;
        }

        action = action || null;

        if (action === null) {
            return true;
        }
        if (!(action in data)) {
            return false;
        }

        var value = data[action];

        if (value === 'all') {
            return true;
        }

        if (value === 'yes') {
            return true;
        }

        if (value === 'no') {
            return false;
        }

        if (typeof isOwner === 'undefined') {
            return true;
        }

        if (isOwner) {
            if (value === 'own' || value === 'team') {
                return true;
            }
        }

        let result = false;

        if (value === 'team') {
            result = inTeam;

            if (inTeam === null) {
                if (precise) {
                    result = null;
                }
                else {
                    return true;
                }
            }
            else if (inTeam) {
                return true;
            }
        }

        if (isOwner === null) {
            if (precise) {
                result = null;
            }
            else {
                return true;
            }
        }

        return result;
    }

    /**
     * Check access to model (entity).
     *
     * @param {module:model} model A model.
     * @param {Object.<string, string>|string|null} data Access data.
     * @param {module:acl-manager~action|null} [action=null] Action to check.
     * @param {boolean} [precise=false] To return `null` if not enough data is set in a model.
     *   E.g. the `teams` field is not yet loaded.
     * @returns {boolean|null} True if access allowed, null if not enough data to determine.
     */
    checkModel(model, data, action, precise) {
        if (this.getUser().isAdmin()) {
            return true;
        }

        let entityAccessData = {
            isOwner: this.checkIsOwner(model),
            inTeam: this.checkInTeam(model),
        };

        return this.checkScope(data, action, precise, entityAccessData);
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * Check `delete` access to model.
     *
     * @param {module:model} model A model.
     * @param {Object.<string, string>|string|null} data Access data.
     * @param {boolean} [precise=false] To return `null` if not enough data is set in a model.
     *   E.g. the `teams` field is not yet loaded.
     * @returns {boolean} True if access allowed.
     */
    checkModelDelete(model, data, precise) {
        let result = this.checkModel(model, data, 'delete', precise);

        if (result) {
            return true;
        }

        if (data === false) {
            return false;
        }

        let d = data || {};

        if (d.read === 'no') {
            return false;
        }

        if (model.has('createdById')) {
            if (model.get('createdById') === this.getUser().id && this.aclAllowDeleteCreated) {
                if (!model.has('assignedUserId')) {
                    return true;
                }

                if (!model.get('assignedUserId')) {
                    return true;
                }

                if (model.get('assignedUserId') === this.getUser().id) {
                    return true;
                }

            }
        }

        return result;
    }

    /**
     * Check if a user is owner to a model.
     *
     * @param {module:model} model A model.
     * @returns {boolean|null} True if owner. Null if not clear.
     */
    checkIsOwner(model) {
        let result = false;

        if (model.hasField('assignedUser')) {
            if (this.getUser().id === model.get('assignedUserId')) {
                return true;
            }

            if (!model.has('assignedUserId')) {
                result = null;
            }
        }
        else {
            if (model.hasField('createdBy')) {
                if (this.getUser().id === model.get('createdById')) {
                    return true;
                }

                if (!model.has('createdById')) {
                    result = null;
                }
            }
        }

        if (model.hasField('assignedUsers')) {
            if (!model.has('assignedUsersIds')) {
                return null;
            }

            if (~(model.get('assignedUsersIds') || []).indexOf(this.getUser().id)) {
                return true;
            }

            result = false;
        }

        return result;
    }

    /**
     * Check if a user in a team of a model.
     *
     * @param {module:model} model A model.
     * @returns {boolean|null} True if in a team. Null if not enough data to determine.
     */
    checkInTeam(model) {
        var userTeamIdList = this.getUser().getTeamIdList();

        if (!model.has('teamsIds')) {
            if (this.teamsFieldIsForbidden) {
                return true;
            }

            return null;
        }

        let teamIdList = model.getTeamIdList();

        let inTeam = false;

        userTeamIdList.forEach(id => {
            if (~teamIdList.indexOf(id)) {
                inTeam = true;
            }
        });

        return inTeam;
    }
}

Acl.extend = BullView.extend;

export default Acl;
PK]����&�&search-manager.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module search-manager */

/**
 * Search data.
 *
 * @typedef {Object} module:search-manager~data
 *
 * @property {string} [primary] A primary filter.
 * @property {Object.<string, boolean>} [bool] Bool filters.
 * @property {{string: module:search-manager~advancedFilter}} [advanced] Advanced filters (field filters).
 * Contains data needed for both the backend and frontend. Keys are field names.
 */

/**
 * A where item. Sent to the backend.
 *
 * @typedef {Object} module:search-manager~whereItem
 *
 * @property {string} type A type.
 * @property {string} [attribute] An attribute (field).
 * @property {module:search-manager~whereItem[]|string|number|boolean|null} [value] A value.
 * @property {boolean} [dateTime] Is a date-time item.
 * @property {string} [timeZone] A time-zone (for date-time items).
 */

/**
 * An advanced filter
 *
 * @typedef {Object} module:search-manager~advancedFilter
 *
 * @property {string} type A type. E.g. `equals`.
 * @property {string} [attribute] An attribute.
 * @property {*} [value] A value.
 * @property {Object.<string, *>} [data] Additional data for UI.
 */

/**
 * A search manager.
 */
class SearchManager {

    /**
     * @param {module:collection} collection A collection.
     * @param {string|null} type A type. Used for a storage key.
     * @param {module:storage|null} storage A storage.
     * @param {module:date-time|null} dateTime A date-time util.
     * @param {module:search-manager~data|null} [defaultData=null] Default search data.
     * @param {boolean} [emptyOnReset=false] To empty on reset.
     */
    constructor(
        collection,
        type,
        storage,
        dateTime,
        defaultData,
        emptyOnReset
    ) {
        /**
         * @private
         * @type {module:collection}
         */
        this.collection = collection;

        /**
         * An entity type.
         *
         * @public
         * @type {string}
         */
        this.scope = collection.entityType;

        /**
         * @private
         * @type {module:storage|null}
         */
        this.storage = storage;

        /**
         * @private
         * @type {string}
         */
        this.type = type || 'list';

        /**
         * @private
         * @type {module:date-time|null}
         */
        this.dateTime = dateTime;

        /**
         * @private
         * @type {boolean}
         */
        this.emptyOnReset = emptyOnReset;

        /**
         * @private
         * @type {Object}
         */
        this.emptyData = {
            textFilter: '',
            bool: {},
            advanced: {},
            primary: null,
        };

        if (defaultData) {
            this.defaultData = defaultData;

            for (let p in this.emptyData) {
                if (!(p in defaultData)) {
                    defaultData[p] = Espo.Utils.clone(this.emptyData[p]);
                }
            }
        }

        this.data = Espo.Utils.clone(defaultData) || this.emptyData;

        this.sanitizeData();
    }

    /**
     * @private
     */
    sanitizeData() {
        if (!('advanced' in this.data)) {
            this.data.advanced = {};
        }

        if (!('bool' in this.data)) {
            this.data.bool = {};
        }

        if (!('textFilter' in this.data)) {
            this.data.textFilter = '';
        }
    }

    /**
     * Get a where clause. The where clause to be sent to the backend.
     *
     * @returns {module:search-manager~whereItem[]}
     */
    getWhere() {
        let where = [];

        if (this.data.textFilter && this.data.textFilter !== '') {
            where.push({
                type: 'textFilter',
                value: this.data.textFilter
            });
        }

        if (this.data.bool) {
            let o = {
                type: 'bool',
                value: [],
            };

            for (let name in this.data.bool) {
                if (this.data.bool[name]) {
                    o.value.push(name);
                }
            }

            if (o.value.length) {
                where.push(o);
            }
        }

        if (this.data.primary) {
            let o = {
                type: 'primary',
                value: this.data.primary,
            };

            if (o.value.length) {
                where.push(o);
            }
        }

        if (this.data.advanced) {
            for (let name in this.data.advanced) {
                let defs = this.data.advanced[name];

                if (!defs) {
                    continue;
                }

                let part = this.getWherePart(name, defs);

                where.push(part);
            }
        }

        return where;
    }

    /**
     * @private
     */
    getWherePart(name, defs) {
        let attribute = name;

        if (typeof defs !== 'object') {
            console.error('Bad where clause');

            return {};
        }

        if ('where' in defs) {
            return defs.where;
        }

        let type = defs.type;
        let value;

        if (type === 'or' || type === 'and') {
            let a = [];

            value = defs.value || {};

            for (let n in value) {
                a.push(this.getWherePart(n, value[n]));
            }

            return {
                type: type,
                value: a
            };
        }

        if ('field' in defs) { // for backward compatibility
            attribute = defs.field;
        }

        if ('attribute' in defs) {
            attribute = defs.attribute;
        }

        if (defs.dateTime) {
            return {
                type: type,
                attribute: attribute,
                value: defs.value,
                dateTime: true,
                timeZone: this.dateTime.timeZone || 'UTC',
            };
        }

        value = defs.value;

        return {
            type: type,
            attribute: attribute,
            value: value
        };
    }

    /**
     * Load stored data.
     *
     * @returns {module:search-manager}
     */
    loadStored() {
        this.data =
            this.storage.get(this.type + 'Search', this.scope) ||
            Espo.Utils.clone(this.defaultData) ||
            Espo.Utils.clone(this.emptyData);

        this.sanitizeData();

        return this;
    }

    /**
     * Get data.
     *
     * @returns {module:search-manager~data}
     */
    get() {
        return this.data;
    }

    /**
     * Set advanced filters.
     *
     * @param {Object.<string, module:search-manager~advancedFilter>} advanced Advanced filters.
     *   Pairs of field => advancedFilter.
     */
    setAdvanced(advanced) {
        this.data = Espo.Utils.clone(this.data);

        this.data.advanced = advanced;
    }

    /**
     * Set bool filters.
     *
     * @param {Object.<string, boolean>} bool Bool filters.
     */
    setBool(bool) {
        this.data = Espo.Utils.clone(this.data);

        this.data.bool = bool;
    }

    /**
     * Set a primary filter.
     *
     * @param {string} primary A filter.
     */
    setPrimary(primary) {
        this.data = Espo.Utils.clone(this.data);

        this.data.primary = primary;
    }

    /**
     * Set data.
     *
     * @param {module:search-manager~data} data Data.
     */
    set(data) {
        this.data = data;

        if (this.storage) {
            data = Espo.Utils.clone(data);
            delete data['textFilter'];

            this.storage.set(this.type + 'Search', this.scope, data);
        }
    }

    /**
     * Empty data.
     */
    empty() {
        this.data = Espo.Utils.clone(this.emptyData);

        if (this.storage) {
            this.storage.clear(this.type + 'Search', this.scope);
        }
    }

    /**
     * Reset.
     */
    reset() {
        if (this.emptyOnReset) {
            this.empty();

            return;
        }

        this.data = Espo.Utils.clone(this.defaultData) || Espo.Utils.clone(this.emptyData);

        if (this.storage) {
            this.storage.clear(this.type + 'Search', this.scope);
        }
    }
}

export default SearchManager;
PK]z蕮Y(Y(ajax.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module ajax */

import $ from 'jquery';
import Utils from 'utils';

let isConfigured = false;
/** @type {number} */
let defaultTimeout;
/** @type {string} */
let apiUrl;
/** @type {Espo.Ajax~Handler} */
let beforeSend;
/** @type {Espo.Ajax~Handler} */
let onSuccess;
/** @type {Espo.Ajax~Handler} */
let onError;
/** @type {Espo.Ajax~Handler} */
let onTimeout;

/**
 * @callback Espo.Ajax~Handler
 * @param {XMLHttpRequest} [xhr]
 * @param {Object.<string, *>} [options]
 */

/**
 * Options.
 *
 * @typedef {Object} Espo.Ajax~Options
 *
 * @property {Number} [timeout] A timeout.
 * @property {Object.<string, string>} [headers] A request headers.
 * @property {'json'|'text'} [dataType] A data type.
 * @property {string} [contentType] A content type.
 * @property {boolean} [resolveWithXhr] To resolve with `XMLHttpRequest`.
 */

const baseUrl = Utils.obtainBaseUrl();

// noinspection JSUnusedGlobalSymbols
/**
 * Functions for API HTTP requests.
 */
const Ajax = Espo.Ajax = {

    /**
     * Request.
     *
     * @param {string} url An URL.
     * @param {'GET'|'POST'|'PUT'|'DELETE'|'PATCH'|'OPTIONS'} method An HTTP method.
     * @param {*} [data] Data.
     * @param {Espo.Ajax~Options & Object.<string, *>} [options] Options.
     * @returns {AjaxPromise<any, XMLHttpRequest>}
     */
    request: function (url, method, data, options) {
        options = options || {};

        let timeout = 'timeout' in options ? options.timeout : defaultTimeout;
        let contentType = options.contentType || 'application/json';
        let body;

        if (options.data && !data) {
            data = options.data;
        }

        if (apiUrl) {
            url = Espo.Utils.trimSlash(apiUrl) + '/' + url;
        }

        if (!['GET', 'OPTIONS'].includes(method) && data) {
            body = data;

            if (contentType === 'application/json' && typeof data !== 'string') {
                body = JSON.stringify(data);
            }
        }

        if (method === 'GET' && data) {
            let part = $.param(data);

            url.includes('?') ?
                url += '&' :
                url += '?';

            url += part;
        }

        let urlObj = new URL(baseUrl + url);

        let xhr = new Xhr();
        xhr.timeout = timeout;
        xhr.open(method, urlObj);
        xhr.setRequestHeader('Content-Type', contentType);

        if (options.headers) {
            for (let key in options.headers) {
                xhr.setRequestHeader(key, options.headers[key]);
            }
        }

        if (beforeSend) {
            beforeSend(xhr, options);
        }

        let promiseWrapper = {};

        let promise = new AjaxPromise((resolve, reject) => {
            const onErrorGeneral = (isTimeout) => {
                if (options.error) {
                    options.error(xhr, options);
                }

                reject(xhr, options);

                if (isTimeout) {
                    if (onTimeout) {
                        onTimeout(xhr, options);
                    }

                    return;
                }

                if (onError) {
                    onError(xhr, options);
                }
            };

            xhr.ontimeout = () => onErrorGeneral(true);
            xhr.onerror = () => onErrorGeneral();

            xhr.onload = () => {
                if (xhr.status >= 400) {
                    onErrorGeneral();

                    return;
                }

                let response = xhr.responseText;

                if ((options.dataType || 'json') === 'json') {
                    try {
                        response = JSON.parse(xhr.responseText);
                    }
                    catch (e) {
                        console.error('Could not parse API response.');

                        onErrorGeneral();
                    }
                }

                if (options.success) {
                    options.success(response);
                }

                onSuccess(xhr, options);

                if (options.resolveWithXhr) {
                    response = xhr;
                }

                resolve(response)
            }

            xhr.send(body);

            if (promiseWrapper.promise) {
                promiseWrapper.promise.xhr = xhr;

                return;
            }

            promiseWrapper.xhr = xhr;
        });

        promiseWrapper.promise = promise;
        promise.xhr = promise.xhr || promiseWrapper.xhr;

        return promise;
    },

    /**
     * POST request.
     *
     * @param {string} url An URL.
     * @param {*} [data] Data.
     * @param {Espo.Ajax~Options & Object.<string, *>} [options] Options.
     * @returns {Promise<any, XMLHttpRequest>}
     */
    postRequest: function (url, data, options) {
        if (data) {
            data = JSON.stringify(data);
        }

        return /** @type {Promise<any>} */ Ajax.request(url, 'POST', data, options);
    },

    /**
     * PATCH request.
     *
     * @param {string} url An URL.
     * @param {*} [data] Data.
     * @param {Espo.Ajax~Options & Object.<string, *>} [options] Options.
     * @returns {Promise<any, XMLHttpRequest>}
     */
    patchRequest: function (url, data, options) {
        if (data) {
            data = JSON.stringify(data);
        }

        return /** @type {Promise<any>} */ Ajax.request(url, 'PATCH', data, options);
    },

    /**
     * PUT request.
     *
     * @param {string} url An URL.
     * @param {*} [data] Data.
     * @param {Espo.Ajax~Options & Object.<string, *>} [options] Options.
     * @returns {Promise<any, XMLHttpRequest>}
     */
    putRequest: function (url, data, options) {
        if (data) {
            data = JSON.stringify(data);
        }

        return /** @type {Promise<any>} */ Ajax.request(url, 'PUT', data, options);
    },

    /**
     * DELETE request.
     *
     * @param {string} url An URL.
     * @param {*} [data] Data.
     * @param {Espo.Ajax~Options & Object.<string, *>} [options] Options.
     * @returns {Promise<any, XMLHttpRequest>}
     */
    deleteRequest: function (url, data, options) {
        if (data) {
            data = JSON.stringify(data);
        }

        return /** @type {Promise<any>} */ Ajax.request(url, 'DELETE', data, options);
    },

    /**
     * GET request.
     *
     * @param {string} url An URL.
     * @param {*} [data] Data.
     * @param {Espo.Ajax~Options & Object.<string, *>} [options] Options.
     * @returns {Promise<any, XMLHttpRequest>}
     */
    getRequest: function (url, data, options) {
        return /** @type {Promise<any>} */ Ajax.request(url, 'GET', data, options);
    },

    /**
     * @internal
     * @param {{
     *     apiUrl: string,
     *     timeout: number,
     *     beforeSend: Espo.Ajax~Handler,
     *     onSuccess: Espo.Ajax~Handler,
     *     onError: Espo.Ajax~Handler,
     *     onTimeout: Espo.Ajax~Handler,
     * }} options Options.
     */
    configure: function (options) {
        if (isConfigured) {
            throw new Error("Ajax is already configured.");
        }

        apiUrl = options.apiUrl;
        defaultTimeout = options.timeout;
        beforeSend = options.beforeSend;
        onSuccess = options.onSuccess;
        onError = options.onError;
        onTimeout = options.onTimeout;

        isConfigured = true;
    },
};

/**
 * @memberOf module:ajax
 */
class AjaxPromise extends Promise {

    /**
     * @type {XMLHttpRequest|null}
     * @internal
     */
    xhr = null

    isAborted = false

    /**
     * @deprecated Use `catch`.
     * @todo Remove in v9.0.
     */
    fail(...args) {
        return this.catch(args[0]);
    }
    /**
     * @deprecated Use `then`
     * @todo Remove in v9.0.
     */
    done(...args) {
        return this.then(args[0]);
    }

    /**
     * Abort the request.
     */
    abort() {
        this.isAborted = true;

        if (this.xhr) {
            this.xhr.abort();
        }
    }

    /**
     * Get a ready state.
     *
     * @return {Number}
     */
    getReadyState() {
        if (!this.xhr) {
            return 0;
        }

        return this.xhr.readyState || 0;
    }

    /**
     * Get a status code
     *
     * @return {Number}
     */
    getStatus() {
        if (!this.xhr) {
            return 0;
        }

        return this.xhr.status;
    }
}

/**
 * @name module:ajax.Xhr
 */
class Xhr extends XMLHttpRequest {
    /**
     * To be set in an error handler to bypass default handling.
     */
    errorIsHandled = false
}

export default Ajax;
PK]@0�CFCF
controller.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module controller */

import Exceptions from 'exceptions';
import {Events, View as BullView} from 'bullbone';
import $ from 'jquery';

/**
 * @callback module:controller~viewCallback
 * @param {module:view} view A view.
 */

/**
 * @callback module:controller~masterViewCallback
 * @param {module:views/site/master} view A master view.
 */


/**
 * A controller. To be extended.
 *
 * @mixes Bull.Events
 */
class Controller {

    /**
     * @internal
     * @param {Object.<string, *>} params
     * @param {Object} injections
     */
    constructor(params, injections) {
        this.params = params || {};

        /** @type {module:controllers/base} */
        this.baseController = injections.baseController;
        /** @type {Bull.Factory} */
        this.viewFactory = injections.viewFactory;
        /** @type {module:model} */
        this.modelFactory = injections.modelFactory;
        /** @type {module:collection-factory} */
        this.collectionFactory = injections.collectionFactory;

        this._settings = injections.settings || null;
        this._user = injections.user || null;
        this._preferences = injections.preferences || null;
        this._acl = injections.acl || null;
        this._cache = injections.cache || null;
        this._router = injections.router || null;
        this._storage = injections.storage || null;
        this._metadata = injections.metadata || null;
        this._dateTime = injections.dateTime || null;
        this._broadcastChannel = injections.broadcastChannel || null;

        if (!this.baseController) {
            this.on('logout', () => this.clearAllStoredMainViews());
        }

        this.set('masterRendered', false);
    }

    /**
     * A default action.
     *
     * @type {string}
     */
    defaultAction = 'index'

    /**
     * A name.
     *
     * @type {string|null}
     */
    name = null

    /**
     * Params.
     *
     * @type {Object}
     * @private
     */
    params = null

    /**
     * A view factory.
     *
     * @type {Bull.Factory}
     * @protected
     */
    viewFactory = null

    /**
     * A model factory.
     *
     * @type {module:model-factory}
     * @protected
     */
    modelFactory = null

    /**
     * A body view.
     *
     * @public
     * @type {string|null}
     */
    masterView = null

    /**
     * Set the router.
     *
     * @internal
     * @param {module:router} router
     */
    setRouter(router) {
        this._router = router;

        this.trigger('router-set', router);
    }

    /**
     * @protected
     * @returns {module:models/settings}
     */
    getConfig() {
        return this._settings;
    }

    /**
     * @protected
     * @returns {module:models/user}
     */
    getUser() {
        return this._user;
    }

    /**
     * @protected
     * @returns {module:models/preferences}
     */
    getPreferences() {
        return this._preferences;
    }

    /**
     * @protected
     * @returns {module:acl-manager}
     */
    getAcl() {
        return this._acl;
    }

    /**
     * @protected
     * @returns {module:cache}
     */
    getCache() {
        return this._cache;
    }

    /**
     * @protected
     * @returns {module:router}
     */
    getRouter() {
        return this._router;
    }

    /**
     * @protected
     * @returns {module:storage}
     */
    getStorage() {
        return this._storage;
    }

    /**
     * @protected
     * @returns {module:metadata}
     */
    getMetadata() {
        return this._metadata;
    }

    /**
     * @protected
     * @returns {module:date-time}
     */
    getDateTime() {
        return this._dateTime;
    }

    /**
     * Get a parameter of all controllers.
     *
     * @param {string} key A key.
     * @return {*} Null if a key doesn't exist.
     */
    get(key) {
        if (key in this.params) {
            return this.params[key];
        }

        return null;
    }

    /**
     * Set a parameter for all controllers.
     *
     * @param {string} key A name of a view.
     * @param {*} value
     */
    set(key, value) {
        this.params[key] = value;
    }

    /**
     * Unset a parameter.
     *
     * @param {string} key A key.
     */
    unset(key) {
        delete this.params[key];
    }

    /**
     * Has a parameter.
     *
     * @param {string} key A key.
     * @returns {boolean}
     */
    has(key) {
        return key in this.params;
    }

    /**
     * Get a stored main view.
     *
     * @param {string} key A key.
     * @returns {module:view|null}
     */
    getStoredMainView(key) {
        return this.get('storedMainView-' + key);
    }

    /**
     * Has a stored main view.
     * @param {string} key
     * @returns {boolean}
     */
    hasStoredMainView(key) {
        return this.has('storedMainView-' + key);
    }

    /**
     * Clear a stored main view.
     * @param {string} key
     */
    clearStoredMainView(key) {
        let view = this.getStoredMainView(key);

        if (view) {
            view.remove(true);
        }

        this.unset('storedMainView-' + key);
    }

    /**
     * Store a main view.
     *
     * @param {string} key A key.
     * @param {module:view} view A view.
     */
    storeMainView(key, view) {
        this.set('storedMainView-' + key, view);

        this.listenTo(view, 'remove', (o) => {
            o = o || {};

            if (o.ignoreCleaning) {
                return;
            }

            this.stopListening(view, 'remove');

            this.clearStoredMainView(key);
        });
    }

    /**
     * Clear all stored main views.
     */
    clearAllStoredMainViews() {
        for (let k in this.params) {
            if (k.indexOf('storedMainView-') !== 0) {
                continue;
            }

            let key = k.slice(15);

            this.clearStoredMainView(key);
        }
    }

    /**
     * Check access to an action.
     *
     * @param {string} action An action.
     * @returns {boolean}
     */
    checkAccess(action) {
        return true;
    }

    /**
     * Process access check to the controller.
     */
    handleAccessGlobal() {
        if (!this.checkAccessGlobal()) {
            throw new Exceptions.AccessDenied("Denied access to '" + this.name + "'");
        }
    }

    /**
     * Check access to the controller.
     *
     * @returns {boolean}
     */
    checkAccessGlobal() {
        return true;
    }

    /**
     * Check access to an action. Throwing an exception.
     *
     * @param {string} action An action.
     */
    handleCheckAccess(action) {
        if (this.checkAccess(action)) {
            return;
        }

        const msg = action ?
            "Denied access to action '" + this.name + "#" + action + "'" :
            "Denied access to scope '" + this.name + "'";

        throw new Exceptions.AccessDenied(msg);
    }

    /**
     * Process an action.
     *
     * @param {string} action
     * @param {Object} options
     */
    doAction(action, options) {
        this.handleAccessGlobal();

        action = action || this.defaultAction;

        let method = 'action' + Espo.Utils.upperCaseFirst(action);

        if (!(method in this)) {
            throw new Exceptions.NotFound("Action '" + this.name + "#" + action + "' is not found");
        }

        let preMethod = 'before' + Espo.Utils.upperCaseFirst(action);
        let postMethod = 'after' + Espo.Utils.upperCaseFirst(action);

        if (preMethod in this) {
            this[preMethod].call(this, options || {});
        }

        this[method].call(this, options || {});

        if (postMethod in this) {
            this[postMethod].call(this, options || {});
        }
    }

    /**
     * Serve a master view. Render if not already rendered.
     *
     * @param {module:controller~masterViewCallback} callback A callback with a created master view.
     * @private
     */
    master(callback) {
        const entire = this.get('entire');

        if (entire) {
            entire.remove();

            this.set('entire', null);
        }

        const master = this.get('master');

        if (master) {
            callback.call(this, master);

            return;
        }

        let masterView = this.masterView || 'views/site/master';

        this.viewFactory.create(masterView, {fullSelector: 'body'}, /** module:view */master => {
            this.set('master', master);

            if (this.get('masterRendered')) {
                callback.call(this, master);

                return;
            }

            master.render()
                .then(() => {
                    this.set('masterRendered', true);

                    callback.call(this, master);
                })
        });
    }

    /**
     * @param {module:views/site/master} masterView
     * @private
     */
    _unchainMainView(masterView) {
        if (
            !masterView.currentViewKey ||
            !this.hasStoredMainView(masterView.currentViewKey)
        ) {
            return;
        }

        const currentMainView = masterView.getView('main');

        if (!currentMainView) {
            return;
        }

        currentMainView.propagateEvent('remove', {ignoreCleaning: true});
        masterView.unchainView('main');
    }

    /**
     * @typedef {Object} module:controller~mainParams
     * @property {boolean} [useStored] Use a stored view if available.
     * @property {string} [key] A stored view key.
     */

    /**
     * Create a main view in the master container and render it.
     *
     * @param {string|module:view} [view] A view name or view instance.
     * @param {Object.<string, *>} [options] Options for a view.
     * @param {module:controller~viewCallback} [callback] A callback with a created view.
     * @param {module:controller~mainParams} [params] Parameters.
     */
    main(view, options, callback, params = {}) {
        const dto = {
            isCanceled: false,
            key: params.key,
            useStored: params.useStored,
            callback: callback,
        };

        const selector = '#main';

        const useStored = params.useStored || false;
        const key = params.key;

        this.listenToOnce(this.baseController, 'action', () => dto.isCanceled = true);

        const mainView = view && typeof view === 'object' ?
            view : undefined;

        const viewName = !mainView ?
            (view || 'views/base') : undefined;

        this.master(masterView => {
            if (dto.isCanceled) {
                return;
            }

            options = options || {};
            options.fullSelector = selector;

            if (useStored && this.hasStoredMainView(key)) {
                const mainView = this.getStoredMainView(key);

                let isActual = true;

                if (
                    mainView &&
                    ('isActualForReuse' in mainView) &&
                    typeof mainView.isActualForReuse === 'function'
                ) {
                    isActual = mainView.isActualForReuse();
                }

                let lastUrl = (mainView && 'lastUrl' in mainView) ? mainView.lastUrl : null;

                if (
                    isActual &&
                    (!lastUrl || lastUrl === this.getRouter().getCurrentUrl())
                ) {
                    this._processMain(mainView, masterView, dto);

                    if (
                        'setupReuse' in mainView &&
                        typeof mainView.setupReuse === 'function'
                    ) {
                        mainView.setupReuse(options.params || {});
                    }

                    return;
                }

                this.clearStoredMainView(key);
            }

            if (mainView) {
                this._unchainMainView(masterView);

                masterView.assignView('main', mainView, selector)
                    .then(() => {
                        dto.isSet = true;

                        this._processMain(view, masterView, dto);
                    });

                return;
            }

            this.viewFactory.create(viewName, options, view => {
                this._processMain(view, masterView, dto);
            });
        });
    }

    /**
     * @param {module:view} mainView
     * @param {module:views/site/master} masterView
     * @param {{
     *     isCanceled: boolean,
     *     key?: string,
     *     useStored?: boolean,
     *     callback?: module:controller~viewCallback,
     *     isSet?: boolean,
     * }} dto Data.
     * @private
     */
    _processMain(mainView, masterView, dto) {
        if (dto.isCanceled) {
            return;
        }

        const key = dto.key;

        if (key) {
            this.storeMainView(key, mainView);
        }

        const onAction = () => {
            mainView.cancelRender();
            dto.isCanceled = true;
        };

        mainView.listenToOnce(this.baseController, 'action', onAction);

        if (masterView.currentViewKey) {
            this.set('storedScrollTop-' + masterView.currentViewKey, $(window).scrollTop());

            this._unchainMainView(masterView);
        }

        masterView.currentViewKey = key;

        if (!dto.isSet) {
            masterView.setView('main', mainView);
        }

        const afterRender = () => {
            setTimeout(() => mainView.stopListening(this.baseController, 'action', onAction), 500);

            mainView.updatePageTitle();

            if (dto.useStored && this.has('storedScrollTop-' + key)) {
                $(window).scrollTop(this.get('storedScrollTop-' + key));

                return;
            }

            $(window).scrollTop(0);
        };

        if (dto.callback) {
            this.listenToOnce(mainView, 'after:render', afterRender);

            dto.callback.call(this, mainView);

            return;
        }

        mainView.render()
            .then(afterRender);
    }

    /**
     * Show a loading notify-message.
     */
    showLoadingNotification() {
        let master = this.get('master');

        if (!master) {
            return;
        }

        master.showLoadingNotification();
    }

    /**
     * Hide a loading notify-message.
     */
    hideLoadingNotification() {
        let master = this.get('master');

        if (!master) {
            return;
        }

        master.hideLoadingNotification();
    }

    /**
     * Create a view in the BODY element. Use for rendering separate pages without the default navbar and footer.
     * If a callback is not passed, the view will be automatically rendered.
     *
     * @param {string|module:view} view A view name or view instance.
     * @param {Object.<string, *>} [options] Options for a view.
     * @param {module:controller~viewCallback} [callback] A callback with a created view.
     */
    entire(view, options, callback) {
        const masterView = this.get('master');

        if (masterView) {
            masterView.remove();
        }

        this.set('master', null);
        this.set('masterRendered', false);

        if (typeof view === 'object') {
            view.setElement('body');

            this.viewFactory.prepare(view, () => {
                if (!callback) {
                    view.render();

                    return;
                }

                callback(view);
            });

            return;
        }

        options = options || {};
        options.fullSelector = 'body';

        this.viewFactory.create(view, options, view => {
            this.set('entire', view);

            if (!callback) {
                view.render();

                return;
            }

            callback(view);
        });
    }
}

Object.assign(Controller.prototype, Events);

/** For backward compatibility. */
Controller.extend = BullView.extend;

export default Controller;
PK]* r��namespace.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

var Espo = {};
window.Espo = Espo;
PK]�
]��9�9controllers/admin.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import Controller from 'controller';
import SearchManager from 'search-manager';
import SettingsEditView from 'views/settings/edit';
import AdminIndexView from 'views/admin/index';

class AdminController extends Controller {

    checkAccessGlobal() {
        if (this.getUser().isAdmin()) {
            return true;
        }

        return false;
    }

    // noinspection JSUnusedGlobalSymbols
    actionPage(options) {
        let page = options.page;

        if (options.options) {
            options = {
                ...Espo.Utils.parseUrlOptionsParam(options.options),
                ...options,
            };

            delete options.options;
        }

        if (!page) {
            throw new Error();
        }

        let methodName = 'action' + Espo.Utils.upperCaseFirst(page);

        if (this[methodName]) {
            this[methodName](options);

            return;
        }

        let defs = this.getPageDefs(page);

        if (!defs) {
            throw new Espo.Exceptions.NotFound();
        }

        if (defs.view) {
            this.main(defs.view, options);

            return;
        }

        if (!defs.recordView) {
            throw new Espo.Exceptions.NotFound();
        }

        let model = this.getSettingsModel();

        model.fetch().then(() => {
            model.id = '1';

            const editView = new SettingsEditView({
                model: model,
                headerTemplate: 'admin/settings/headers/page',
                recordView: defs.recordView,
                page: page,
                label: defs.label,
                optionsToPass: [
                    'page',
                    'label',
                ],
            });

            this.main(editView);
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionIndex(options) {
        let isReturn = options.isReturn;
        let key = this.name + 'Index';

        if (this.getRouter().backProcessed) {
            isReturn = true;
        }

        if (!isReturn && this.getStoredMainView(key)) {
            this.clearStoredMainView(key);
        }

        const view = new AdminIndexView();

        this.main(view, null, view => {
            view.render();

            this.listenTo(view, 'clear-cache', this.clearCache);
            this.listenTo(view, 'rebuild', this.rebuild);
        }, {
            useStored: isReturn,
            key: key,
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionUsers() {
        this.getRouter().dispatch('User', 'list', {fromAdmin: true});
    }

    // noinspection JSUnusedGlobalSymbols
    actionPortalUsers() {
        this.getRouter().dispatch('PortalUser', 'list', {fromAdmin: true});
    }

    // noinspection JSUnusedGlobalSymbols
    actionApiUsers() {
        this.getRouter().dispatch('ApiUser', 'list', {fromAdmin: true});
    }

    // noinspection JSUnusedGlobalSymbols
    actionTeams() {
        this.getRouter().dispatch('Team', 'list', {fromAdmin: true});
    }

    // noinspection JSUnusedGlobalSymbols
    actionRoles() {
        this.getRouter().dispatch('Role', 'list', {fromAdmin: true});
    }

    // noinspection JSUnusedGlobalSymbols
    actionPortalRoles() {
        this.getRouter().dispatch('PortalRole', 'list', {fromAdmin: true});
    }

    // noinspection JSUnusedGlobalSymbols
    actionPortals() {
        this.getRouter().dispatch('Portal', 'list', {fromAdmin: true});
    }

    // noinspection JSUnusedGlobalSymbols
    actionLeadCapture() {
        this.getRouter().dispatch('LeadCapture', 'list', {fromAdmin: true});
    }

    // noinspection JSUnusedGlobalSymbols
    actionEmailFilters() {
        this.getRouter().dispatch('EmailFilter', 'list', {fromAdmin: true});
    }

    // noinspection JSUnusedGlobalSymbols
    actionGroupEmailFolders() {
        this.getRouter().dispatch('GroupEmailFolder', 'list', {fromAdmin: true});
    }

    // noinspection JSUnusedGlobalSymbols
    actionEmailTemplates() {
        this.getRouter().dispatch('EmailTemplate', 'list', {fromAdmin: true});
    }

    // noinspection JSUnusedGlobalSymbols
    actionPdfTemplates() {
        this.getRouter().dispatch('Template', 'list', {fromAdmin: true});
    }

    // noinspection JSUnusedGlobalSymbols
    actionDashboardTemplates() {
        this.getRouter().dispatch('DashboardTemplate', 'list', {fromAdmin: true});
    }

    // noinspection JSUnusedGlobalSymbols
    actionWebhooks() {
        this.getRouter().dispatch('Webhook', 'list', {fromAdmin: true});
    }

    // noinspection JSUnusedGlobalSymbols
    actionLayoutSets() {
        this.getRouter().dispatch('LayoutSet', 'list', {fromAdmin: true});
    }

    // noinspection JSUnusedGlobalSymbols
    actionWorkingTimeCalendar() {
        this.getRouter().dispatch('WorkingTimeCalendar', 'list', {fromAdmin: true});
    }

    // noinspection JSUnusedGlobalSymbols
    actionAttachments() {
        this.getRouter().dispatch('Attachment', 'list', {fromAdmin: true});
    }

    // noinspection JSUnusedGlobalSymbols
    actionAuthenticationProviders() {
        this.getRouter().dispatch('AuthenticationProvider', 'list', {fromAdmin: true});
    }

    // noinspection JSUnusedGlobalSymbols
    actionEmailAddresses() {
        this.getRouter().dispatch('EmailAddress', 'list', {fromAdmin: true});
    }

    // noinspection JSUnusedGlobalSymbols
    actionPhoneNumbers() {
        this.getRouter().dispatch('PhoneNumber', 'list', {fromAdmin: true});
    }

    // noinspection JSUnusedGlobalSymbols
    actionPersonalEmailAccounts() {
        this.getRouter().dispatch('EmailAccount', 'list', {fromAdmin: true});
    }

    // noinspection JSUnusedGlobalSymbols
    actionGroupEmailAccounts() {
        this.getRouter().dispatch('InboundEmail', 'list', {fromAdmin: true});
    }

    // noinspection JSUnusedGlobalSymbols
    actionActionHistory() {
        this.getRouter().dispatch('ActionHistoryRecord', 'list', {fromAdmin: true});
    }

    // noinspection JSUnusedGlobalSymbols
    actionImport() {
        this.getRouter().dispatch('Import', 'index', {fromAdmin: true});
    }

    // noinspection JSUnusedGlobalSymbols
    actionLayouts(options) {
        var scope = options.scope || null;
        var type = options.type || null;
        var em = options.em || false;

        this.main('views/admin/layouts/index', {scope: scope, type: type, em: em});
    }

    // noinspection JSUnusedGlobalSymbols
    actionLabelManager(options) {
        var scope = options.scope || null;
        var language = options.language || null;

        this.main('views/admin/label-manager/index', {scope: scope, language: language});
    }

    // noinspection JSUnusedGlobalSymbols
    actionTemplateManager(options) {
        var name = options.name || null;

        this.main('views/admin/template-manager/index', {name: name});
    }

    // noinspection JSUnusedGlobalSymbols
    actionFieldManager(options) {
        var scope = options.scope || null;
        var field = options.field || null;

        this.main('views/admin/field-manager/index', {scope: scope, field: field});
    }

    // noinspection JSUnusedGlobalSymbols
    actionEntityManager(options) {
        var scope = options.scope || null;

        if (scope && options.edit) {
            this.main('views/admin/entity-manager/edit', {scope: scope});

            return;
        }

        if (options.create) {
            this.main('views/admin/entity-manager/edit');

            return;
        }

        if (scope && options.formula) {
            this.main('views/admin/entity-manager/formula', {scope: scope, type: options.type});

            return;
        }

        if (scope) {
            this.main('views/admin/entity-manager/scope', {scope: scope});

            return;
        }

        this.main('views/admin/entity-manager/index');
    }

    // noinspection JSUnusedGlobalSymbols
    actionLinkManager(options) {
        var scope = options.scope || null;

        this.main('views/admin/link-manager/index', {scope: scope});
    }

    // noinspection JSUnusedGlobalSymbols
    actionSystemRequirements() {
        this.main('views/admin/system-requirements/index');
    }

    /**
     * @returns {module:models/settings}
     */
    getSettingsModel() {
        let model = this.getConfig().clone();
        model.defs = this.getConfig().defs;

        this.listenTo(model, 'after:save', () => {
            this.getConfig().load();

            this._broadcastChannel.postMessage('update:config');
        });

        return model;
    }

    // noinspection JSUnusedGlobalSymbols
    actionAuthTokens() {
        this.collectionFactory.create('AuthToken', collection => {
            const searchManager = new SearchManager(
                collection,
                'list',
                this.getStorage(),
                this.getDateTime()
            );

            searchManager.loadStored();
            collection.where = searchManager.getWhere();
            collection.maxSize = this.getConfig().get('recordsPerPage') || collection.maxSize;

            this.main('views/admin/auth-token/list', {
                scope: 'AuthToken',
                collection: collection,
                searchManager: searchManager
            });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionAuthLog() {
        this.collectionFactory.create('AuthLogRecord', collection => {
            const searchManager = new SearchManager(
                collection,
                'list',
                this.getStorage(),
                this.getDateTime()
            );

            searchManager.loadStored();

            collection.where = searchManager.getWhere();
            collection.maxSize = this.getConfig().get('recordsPerPage') || collection.maxSize;

            this.main('views/admin/auth-log-record/list', {
                scope: 'AuthLogRecord',
                collection: collection,
                searchManager: searchManager
            });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionJobs() {
        this.collectionFactory.create('Job', collection => {
            const searchManager = new SearchManager(
                collection,
                'list',
                this.getStorage(),
                this.getDateTime()
            );

            searchManager.loadStored();

            collection.where = searchManager.getWhere();
            collection.maxSize = this.getConfig().get('recordsPerPage') || collection.maxSize;

            this.main('views/admin/job/list', {
                scope: 'Job',
                collection: collection,
                searchManager: searchManager,
            });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionIntegrations(options) {
        var integration = options.name || null;

        this.main('views/admin/integrations/index', {integration: integration});
    }

    // noinspection JSUnusedGlobalSymbols
    actionExtensions() {
        this.main('views/admin/extensions/index');
    }

    rebuild() {
        if (this.rebuildRunning) {
            return;
        }

        this.rebuildRunning = true;

        let master = this.get('master');

        Espo.Ui.notify(master.translate('pleaseWait', 'messages'));

        Espo.Ajax
            .postRequest('Admin/rebuild')
            .then(() => {
                let msg = master.translate('Rebuild has been done', 'labels', 'Admin');

                Espo.Ui.success(msg);

                this.rebuildRunning = false;
            })
            .catch(() => {
                this.rebuildRunning = false;
            });
    }

    clearCache() {
        if (this.clearCacheRunning) {
            return;
        }

        this.clearCacheRunning = true;

        const master = this.get('master');

        Espo.Ui.notify(master.translate('pleaseWait', 'messages'));

        Espo.Ajax.postRequest('Admin/clearCache')
            .then(() => {
                let msg = master.translate('Cache has been cleared', 'labels', 'Admin');

                Espo.Ui.success(msg);

                this.clearCacheRunning = false;
            })
            .catch(() => {
                this.clearCacheRunning = false;
            });
    }

    /**
     * @returns {Object|null}
     */
    getPageDefs(page) {
        let panelsDefs = this.getMetadata().get(['app', 'adminPanel']) || {};

        let resultDefs = null;

        for (let panelKey in panelsDefs) {
            let itemList = panelsDefs[panelKey].itemList || [];

            for (let defs of itemList) {
                if (defs.url === '#Admin/' + page) {
                    resultDefs = defs;

                    break;
                }
            }

            if (resultDefs) {
                break;
            }
        }

        return resultDefs;
    }
}

export default AdminController;
PK]O|`�		controllers/address-map.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import Controller from 'controller';

class AddressMapController extends Controller {

    defaultAction = 'index'

    // noinspection JSUnusedGlobalSymbols
    actionIndex() {
        this.error404();
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * @param {Object} o
     */
    actionView(o) {
        this.modelFactory
            .create(o.entityType)
            .then(model => {
                model.id = o.id;

                model.fetch()
                    .then(() => {
                        let viewName = this.getMetadata().get(['AddressMap', 'view']) ||
                            'views/address-map/view';

                        this.main(viewName, {
                            model: model,
                            field: o.field,
                        });
                    });
            });
    }
}

export default AddressMapController;
PK]^6Z��controllers/layout-set.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import RecordController from 'controllers/record';

class LayoutSetController extends RecordController {

    // noinspection JSUnusedGlobalSymbols
    /**
     * @param {Record} options
     */
    actionEditLayouts(options) {
        let id = options.id;

        if (!id) {
            throw new Error("ID not passed.");
        }

        this.main('views/layout-set/layouts', {
            layoutSetId: id,
            scope: options.scope,
            type: options.type,
        });
    }
}

export default LayoutSetController;
PK]���-DDcontrollers/import.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import RecordController from 'controllers/record';

class ImportController extends RecordController {

    defaultAction = 'index'

    checkAccessGlobal() {
        if (this.getAcl().checkScope('Import')) {
            return true;
        }

        return false;
    }

    checkAccess(action) {
        if (this.getAcl().checkScope('Import')) {
            return true;
        }

        return false;
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * @param {{
     *     step?: int|string,
     *     fromAdmin?: boolean,
     *     formData?: Object
     * }} o
     */
    actionIndex(o) {
        o = o || {};

        let step = null;

        if (o.step) {
            step = parseInt(step);
        }

        let formData = null;
        let fileContents = null;

        if (this.storedData) {
            formData = this.storedData.formData;
            fileContents = this.storedData.fileContents;
        }

        if (!formData) {
            step = null;
        }

        formData = formData || o.formData;

        this.main('views/import/index', {
            step: step,
            formData: formData,
            fileContents: fileContents,
            fromAdmin: o.fromAdmin,
        }, /** module:views/import/index */ view => {
            this.listenTo(view, 'change', () => {
                this.storedData = {
                    formData: view.formData,
                    fileContents: view.fileContents,
                };
            });

            this.listenTo(view, 'done', () => {
                delete this.storedData;
            });

            view.render();
        });
    }
}

export default ImportController;
PK]�Ȍp��controllers/last-viewed.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import RecordController from 'controllers/record';

class LastViewedController extends RecordController {

    entityType = 'ActionHistoryRecord'

    checkAccess(action) {
        return this.getAcl().check(this.entityType, action);
    }
}

// noinspection JSUnusedGlobalSymbols
export default LastViewedController;
PK]�)��
�
controllers/portal-user.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import RecordController from 'controllers/record';

class PortalUserController extends RecordController {

    entityType = 'User'

    getCollection(usePreviouslyFetched) {
        return super.getCollection()
            .then(collection => {
                collection.data.userType = 'portal';

                return collection;
            });
    }

    /**
     * @protected
     * @param {Object} options
     * @param {module:models/user} model
     * @param {string} view
     */
    createViewView(options, model, view) {
        if (!model.isPortal()) {
            if (model.isApi()) {
                this.getRouter().dispatch('ApiUser', 'view', {id: model.id, model: model});

                return;
            }

            this.getRouter().dispatch('User', 'view', {id: model.id, model: model});

            return;
        }

        super.createViewView(options, model, view);
    }

    actionCreate(options) {
        options = options || {};
        options.attributes = options.attributes  || {};
        options.attributes.type = 'portal';

        super.actionCreate(options);
    }

    checkAccess(action) {
        if (this.getAcl().getPermissionLevel('portalPermission') === 'yes') {
            return true;
        }

        return false;
    }
}

export default PortalUserController;
PK]�0�K��controllers/dashboard.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import Controller from 'controller';

class DashboardController extends Controller {

    defaultAction = 'index'

    // noinspection JSUnusedGlobalSymbols
    actionIndex() {
        this.main('views/dashboard', {
            displayTitle: true,
        }, view => {
            view.render();
        });
    }
}

export default DashboardController;
PK]<>MQ��controllers/login-as.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import Controller from 'controller';

class LoginAsController extends Controller {

    // noinspection JSUnusedGlobalSymbols
    /**
     * @param {Record} options
     */
    actionLogin(options) {
        let anotherUser = options.anotherUser;
        let username = options.username;

        if (!anotherUser) {
            throw new Error("No anotherUser.");
        }

        this.baseController.login({
            anotherUser: anotherUser,
            username: username,
        });

        this.listenToOnce(this.baseController, 'login', () => {
            this.baseController.once('router-set', () => {
                let url = window.location.href.split('?')[0];

                window.location.replace(url);
            })
        });
    }
}

export default LoginAsController;
PK]���E�	�	controllers/user.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import RecordController from 'controllers/record';

class UserController extends RecordController {

    getCollection(usePreviouslyFetched) {
        return super.getCollection()
            .then(collection => {
                collection.data.userType = 'internal';

                return collection;
            });
    }

    /**
     * @protected
     * @param {Object} options
     * @param {module:models/user} model
     * @param {string} view
     */
    createViewView(options, model, view) {
        if (model.get('deleted')) {
            view = 'views/deleted-detail';

            super.createViewView(options, model, view);

            return;
        }

        if (model.isPortal()) {
            this.getRouter().dispatch('PortalUser', 'view', {id: model.id, model: model});

            return;
        }

        if (model.isApi()) {
            this.getRouter().dispatch('ApiUser', 'view', {id: model.id, model: model});

            return;
        }

        super.createViewView(options, model, view);
    }
}

export default UserController;
PK]�XD�Z	Z	/controllers/lead-capture-opt-in-confirmation.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import Controller from 'controller';

class LeadCaptureOptInConfirmationController extends Controller {

    // noinspection JSUnusedGlobalSymbols
    actionOptInConfirmationSuccess(data) {
        let viewName = this.getMetadata().get(['clientDefs', 'LeadCapture', 'optInConfirmationSuccessView']) ||
            'views/lead-capture/opt-in-confirmation-success';

        this.entire(viewName, {
            resultData: data,
        }, view => {
            view.render();
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionOptInConfirmationExpired(data) {
        let viewName = this.getMetadata().get(['clientDefs', 'LeadCapture', 'optInConfirmationExpiredView']) ||
            'views/lead-capture/opt-in-confirmation-expired';

        this.entire(viewName, {
            resultData: data,
        }, view => {
            view.render();
        });
    }
}

// noinspection JSUnusedGlobalSymbols
export default LeadCaptureOptInConfirmationController;
PK]�tZ��controllers/stream.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import Controller from 'controller';

class StreamController extends Controller {

    defaultAction = 'index'

    // noinspection JSUnusedGlobalSymbols
    actionIndex() {
        this.main('views/stream', {
            displayTitle: true,
        }, view => {
            view.render();
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionPosts() {
        this.main('views/stream', {
            displayTitle: true,
            filter: 'posts',
        }, view => {
            view.render();
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionUpdates() {
        this.main('views/stream', {
            displayTitle: true,
            filter: 'updates',
        }, view => {
            view.render();
        });
    }
}

export default StreamController;
PK]g�o��controllers/portal-role.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import RecordController from 'controllers/record';

class PortalRoleController extends RecordController {

    checkAccess(action) {
        if (this.getUser().isAdmin()) {
            return true;
        }

        return false;
    }
}

export default PortalRoleController;
PK]\Oj�+	+	controllers/external-account.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import Controller from 'controller';

class ExternalAccountController extends Controller {

    defaultAction = 'list'

    actionList() {
        this.collectionFactory.create('ExternalAccount', collection => {
            collection.once('sync', () => {
                this.main('ExternalAccount.Index', {
                    collection: collection,
                });
            });

            collection.fetch();
        });
    }

    /**
     * @param {{id: string}} options
     */
    actionEdit(options) {
        let id = options.id;

        this.collectionFactory.create('ExternalAccount', collection => {
            collection.once('sync', () => {
                this.main('ExternalAccount.Index', {
                    collection: collection,
                    id: id,
                });
            });

            collection.fetch();
        });
    }
}

export default ExternalAccountController;
PK]��-��controllers/record-tree.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import RecordController from 'controllers/record';

class RecordTreeController extends RecordController {

    defaultAction = 'listTree'

    beforeView(options) {
        super.beforeView(options);

        options = options || {};

        if (options.model) {
            options.model.unset('childCollection');
            options.model.unset('childList');
        }
    }

    // noinspection JSUnusedGlobalSymbols
    beforeListTree() {
        this.handleCheckAccess('read');
    }

    // noinspection JSUnusedGlobalSymbols
    actionListTree() {
        this.getCollection().then(collection => {
            collection.url = collection.entityType + '/action/listTree';

            this.main(this.getViewName('listTree'), {
                scope: this.name,
                collection: collection
            });
        });
    }
}

export default RecordTreeController;
PK]��€^^controllers/email.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import RecordController from 'controllers/record';

class EmailController extends RecordController {

    prepareModelView(model, options) {
        super.prepareModelView(model, options);

        this.listenToOnce(model, 'after:send', () => {
            let key = this.name + 'List';
            let stored = this.getStoredMainView(key);

            if (stored) {
                this.clearStoredMainView(key);
            }
        });
    }
}

export default EmailController;
PK]�
�?��controllers/notification.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import Controller from 'controller';

class NotificationController extends Controller {

    defaultAction = 'index'

    // noinspection JSUnusedGlobalSymbols
    actionIndex() {
        this.main('views/notification/list', {}, view => {
            view.render();
        });
    }
}

export default NotificationController;
PK]�I���controllers/base.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module controllers/base */

import Controller from 'controller';
import BaseView from 'views/base';

/**
 * A base controller.
 */
class BaseController extends Controller {

    /**
     * Log in.
     *
     * @param {{
     *     anotherUser?: string,
     *     username?: string,
     * }} [options]
     */
    login(options) {
        let viewName = this.getConfig().get('loginView') || 'views/login';

        let anotherUser = (options || {}).anotherUser;
        let prefilledUsername = (options || {}).username;

        let viewOptions = {
            anotherUser: anotherUser,
            prefilledUsername: prefilledUsername,
        };

        this.entire(viewName, viewOptions, loginView => {
            loginView.render();

            loginView.on('login', (userName, data) => {
                this.trigger('login', this.normalizeLoginData(userName, data));
            });

            loginView.once('redirect', (viewName, headers, userName, password, data) => {
                loginView.remove();

                this.entire(viewName, {
                    loginData: data,
                    userName: userName,
                    password: password,
                    anotherUser: anotherUser,
                    headers: headers,
                }, secondStepView => {
                    secondStepView.render();

                    secondStepView.once('login', (userName, data) => {
                        this.trigger('login', this.normalizeLoginData(userName, data));
                    });

                    secondStepView.once('back', () => {
                        secondStepView.remove();

                        this.login();
                    });
                });
            });
        });
    }

    /** @private */
    normalizeLoginData(userName, data) {
        return {
            auth: {
                userName: userName,
                token: data.token,
                anotherUser: data.anotherUser,
            },
            user: data.user,
            preferences: data.preferences,
            acl: data.acl,
            settings: data.settings,
            appParams: data.appParams,
            language: data.language,
        };
    }

    /**
     * Log out.
     */
    logout() {
        let title = this.getConfig().get('applicationName') || 'EspoCRM';

        $('head title').text(title);

        this.trigger('logout');
    }

    /**
     * Clear cache.
     */
    clearCache() {
        this.entire('views/clear-cache', {
            cache: this.getCache(),
        }, view => {
            view.render();
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionLogin() {
        this.login();
    }

    // noinspection JSUnusedGlobalSymbols
    actionLogout() {
        this.logout();
    }

    // noinspection JSUnusedGlobalSymbols
    actionLogoutWait() {
        this.entire('views/base', {template: 'logout-wait'}, view => {
            view.render()
                .then(() => Espo.Ui.notify(' ... '))
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionClearCache() {
        this.clearCache();
    }

    /**
     * Error Not Found.
     */
    error404() {
        const view = new BaseView({template: 'errors/404'});

        this.entire(view);
    }

    /**
     * Error Forbidden.
     */
    error403() {
        const view = new BaseView({template: 'errors/403'});

        this.entire(view);
    }
}

export default BaseController;
PK]�����controllers/note.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import Controller from 'controller';

class NoteController extends Controller {

    // noinspection JSUnusedGlobalSymbols
    /**
     * @param {Record} options
     */
    actionView(options) {
        let id = options.id;

        if (!id) {
            throw new Espo.Exceptions.NotFound;
        }

        let viewName = this.getMetadata().get(['clientDefs', this.name, 'views', 'detail']) ||
            'views/note/detail';

        let model;

        this.showLoadingNotification();

        this.modelFactory.create('Note')
            .then(m => {
                model = m;
                model.id = id;

                return model.fetch({main: true});
            })
            .then(() => {
                this.hideLoadingNotification();

                this.main(viewName, {model: model});
            });
    }
}

export default NoteController;
PK]�:�

controllers/api-user.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import RecordController from 'controllers/record';

class ApiUserController extends RecordController {

    entityType ='User'

    getCollection(usePreviouslyFetched) {
        return super.getCollection()
            .then(collection => {
                collection.data.userType = 'api';

                return collection;
            });
    }

    /**
     * @protected
     * @param {Object} options
     * @param {module:models/user} model
     * @param {string} view
     */
    createViewView(options, model, view) {
        if (!model.isApi()) {
            if (model.isPortal()) {
                this.getRouter().dispatch('PortalUser', 'view', {id: model.id, model: model});

                return;
            }

            this.getRouter().dispatch('User', 'view', {id: model.id, model: model});

            return;
        }

        super.createViewView(options, model, view);
    }

    actionCreate(options) {
        options = options || {};
        options.attributes = options.attributes  || {};
        options.attributes.type = 'api';

        super.actionCreate(options);
    }
}

export default ApiUserController;
PK]t_�P��controllers/preferences.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import RecordController from 'controllers/record';
import Preferences from 'models/preferences';

class PreferencesController extends RecordController {

    defaultAction = 'own'

    getModel(callback, context) {
        let model = new Preferences();

        model.settings = this.getConfig();
        model.defs = this.getMetadata().get('entityDefs.Preferences');

        if (callback) {
            callback.call(this, model);
        }

        return new Promise(resolve => {
            resolve(model);
        });
    }

    checkAccess(action) {
        return true;
    }

    // noinspection JSUnusedGlobalSymbols
    actionOwn() {
        this.actionEdit({id: this.getUser().id});
    }

    actionList() {}
}

export default PreferencesController;
PK]�t^N��controllers/role.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import RecordController from 'controllers/record';

class RoleController extends RecordController {

    checkAccess(action) {
        if (this.getUser().isAdmin()) {
            return true;
        }

        return false;
    }
}

export default RoleController;
PK]��MaRRcontrollers/home.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import Controller from 'controller';

class HomeController extends Controller {

    // noinspection JSUnusedGlobalSymbols
    actionIndex() {
        this.main('views/home', null);
    }
}

export default HomeController;
PK](׺ɞ�controllers/about.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import Controller from 'controller';

class AboutController extends Controller {

    defaultAction = 'about'

    // noinspection JSUnusedGlobalSymbols
    actionAbout() {
        this.main('About', {}, view => {
            view.render();
        });
    }
}

export default AboutController;
PK]��f��&controllers/password-change-request.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import Controller from 'controller';

class PasswordChangeRequestController extends Controller {

    // noinspection JSUnusedGlobalSymbols
    actionPasswordChange(options) {
        options = options || {};

        if (!options.id) {
            throw new Error();
        }

        this.entire('views/user/password-change-request', {
            requestId: options.id,
            strengthParams: options.strengthParams,
            notFound: options.notFound,
        }, view => {
            view.render();
        });
    }
}

export default PasswordChangeRequestController;
PK]�7���controllers/team.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import RecordController from 'controllers/record';

class TeamController extends RecordController {

    checkAccess(action) {
        if (action === 'read') {
            return true;
        }

        if (this.getUser().isAdmin()) {
            return true;
        }

        return false;
    }
}

export default TeamController;
PK]��昐�controllers/inbound-email.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import RecordController from 'controllers/record';

class InboundEmailController extends RecordController {

    checkAccess(action) {
        if (this.getUser().isAdmin()) {
            return true;
        }

        return false;
    }
}

export default InboundEmailController;
PK]J����:�:controllers/record.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module controllers/record */

import Controller from 'controller';

/**
 * A record controller.
 */
class RecordController extends Controller {

    /** @inheritDoc */
    defaultAction = 'list'

    constructor(params, injections) {
        super(params, injections);

        /**
         * @private
         * @type {Object}
         */
        this.collectionMap = {};
    }

    /** @inheritDoc */
    checkAccess(action) {
        if (this.getAcl().check(this.name, action)) {
            return true;
        }

        return false;
    }

    /**
     * Get a view name/path.
     *
     * @protected
     * @param {'list'|'detail'|'edit'|'create'|'listRelated'|string} type A type.
     * @returns {string}
     */
    getViewName(type) {
        return this.getMetadata().get(['clientDefs', this.name, 'views', type]) ||
            'views/' + Espo.Utils.camelCaseToHyphen(type);
    }

    // noinspection JSUnusedGlobalSymbols
    beforeList() {
        this.handleCheckAccess('read');
    }

    actionList(options) {
        let isReturn = options.isReturn || this.getRouter().backProcessed;

        let key = this.name + 'List';

        if (!isReturn && this.getStoredMainView(key)) {
            this.clearStoredMainView(key);
        }

        this.getCollection().then(collection => {
            const mediator = {};

            const abort = () => {
                collection.abortLastFetch();
                mediator.abort = true;

                Espo.Ui.notify(false);
            };

            this.listenToOnce(this.baseController, 'action', abort);
            this.listenToOnce(collection, 'sync', () => this.stopListening(this.baseController, 'action', abort));

            let viewOptions = {
                scope: this.name,
                collection: collection,
                params: options,
                mediator: mediator,
            };

            const viewName = this.getViewName('list');

            const params = {
                useStored: isReturn,
                key: key,
            };

            this.main(viewName, viewOptions, null, params);
        });
    }

    beforeView() {
        this.handleCheckAccess('read');
    }

    /**
     * @protected
     * @param {Object} options
     * @param {module:model} model
     * @param {string|null} [view]
     */
    createViewView(options, model, view) {
        view = view || this.getViewName('detail');

        this.main(view, {
            scope: this.name,
            model: model,
            returnUrl: options.returnUrl,
            returnDispatchParams: options.returnDispatchParams,
            params: options,
        });
    }

    /**
     * @protected
     * @param {module:model} model
     * @param {Object} options
     */
    prepareModelView(model, options) {}

    // noinspection JSUnusedGlobalSymbols
    /**
     * @param {{
     *     model?: module:model,
     *     id?: string,
     *     isReturn?: boolean,
     *     isAfterCreate?: boolean,
     * }} options
     */
    actionView(options) {
        let id = options.id;

        let isReturn = this.getRouter().backProcessed;

        if (isReturn) {
            if (this.lastViewActionOptions && this.lastViewActionOptions.id === id) {
                options = Espo.Utils.clone(this.lastViewActionOptions);

                if (options.model && options.model.get('deleted')) {
                    delete options.model;
                }
            }

            options.isReturn = true;
        }
        else {
            delete this.lastViewActionOptions;
        }

        this.lastViewActionOptions = options;

        const createView = model => {
            this.prepareModelView(model, options);

            this.createViewView.call(this, options, model);
        };

        if ('model' in options) {
            let model = options.model;

            createView(model);

            this.showLoadingNotification();

            model.fetch()
                .then(() => this.hideLoadingNotification())
                .catch(xhr => {
                    if (
                        xhr.status === 403 &&
                        options.isAfterCreate
                    ) {
                        this.hideLoadingNotification();
                        xhr.errorIsHandled = true;

                        model.trigger('fetch-forbidden');
                    }
                });

            this.listenToOnce(this.baseController, 'action', () => {
                model.abortLastFetch();
                this.hideLoadingNotification();
            });

            return;
        }

        this.getModel().then(model => {
            model.id = id;

            this.showLoadingNotification();

            model.fetch({main: true})
                .then(() => {
                    this.hideLoadingNotification();

                    if (model.get('deleted')) {
                        this.listenToOnce(model, 'after:restore-deleted', () => {
                            createView(model);
                        });

                        this.prepareModelView(model, options);
                        this.createViewView(options, model, 'views/deleted-detail');

                        return;
                    }

                    createView(model);
                });

            this.listenToOnce(this.baseController, 'action', () => {
                model.abortLastFetch();
            });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    beforeCreate() {
        this.handleCheckAccess('create');
    }

    // noinspection JSUnusedLocalSymbols
    /**
     * @protected
     * @param {module:model} model
     * @param {Object} options
     */
    prepareModelCreate(model, options) {
        this.listenToOnce(model, 'before:save', () => {
            let key = this.name + 'List';

            let stored = this.getStoredMainView(key);

            if (!stored) {
                return;
            }

            if (!('storeViewAfterCreate' in stored) || !stored.storeViewAfterCreate) {
                this.clearStoredMainView(key);
            }
        });

        this.listenToOnce(model, 'after:save', () => {
            let key = this.name + 'List';

            let stored = this.getStoredMainView(key);

            if (!stored) {
                return;
            }

            if (!('storeViewAfterCreate' in stored) || !stored.storeViewAfterCreate) {
                return;
            }

            if (!('collection' in stored) || !stored.collection) {
                return;
            }

            this.listenToOnce(stored, 'after:render', () => stored.collection.fetch());
        });
    }

    create(options) {
        options = options || {};

        let optionsOptions = options.options || {};

        this.getModel().then(model => {
            if (options.relate) {
                model.setRelate(options.relate);
            }

            let o = {
                scope: this.name,
                model: model,
                returnUrl: options.returnUrl,
                returnDispatchParams: options.returnDispatchParams,
                params: options,
            };

            for (let k in optionsOptions) {
                o[k] = optionsOptions[k];
            }

            if (options.attributes) {
                model.set(options.attributes);
            }

            this.prepareModelCreate(model, options);

            this.main(this.getViewName('edit'), o);
        });
    }

    actionCreate(options) {
        this.create(options);
    }

    // noinspection JSUnusedGlobalSymbols
    beforeEdit() {
        this.handleCheckAccess('edit');
    }

    // noinspection JSUnusedLocalSymbols
    /**
     * @protected
     * @param {module:model} model
     * @param {Object} options
     */
    prepareModelEdit(model, options) {
        this.listenToOnce(model, 'before:save', () => {
            let key = this.name + 'List';

            let stored = this.getStoredMainView(key);

            if (!stored) {
                return;
            }

            if (!('storeViewAfterUpdate' in stored) || !stored.storeViewAfterUpdate) {
                this.clearStoredMainView(key);
            }
        });
    }

    actionEdit(options) {
        let id = options.id;

        let optionsOptions = options.options || {};

        this.getModel().then(model => {
            model.id = id;

            if (options.model) {
                model = options.model;
            }

            this.prepareModelEdit(model, options);

            this.showLoadingNotification();

            model
                .fetch({main: true})
                .then(() => {
                    this.hideLoadingNotification();

                    let o = {
                        scope: this.name,
                        model: model,
                        returnUrl: options.returnUrl,
                        returnDispatchParams: options.returnDispatchParams,
                        params: options,
                    };

                    for (let k in optionsOptions) {
                        o[k] = optionsOptions[k];
                    }

                    if (options.attributes) {
                        o.attributes = options.attributes;
                    }

                    this.main(this.getViewName('edit'), o);
                });

            this.listenToOnce(this.baseController, 'action', () => {
                model.abortLastFetch();
            });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    beforeMerge() {
        this.handleCheckAccess('edit');
    }

    // noinspection JSUnusedGlobalSymbols
    actionMerge(options) {
        let ids = options.ids.split(',');

        this.getModel().then((model) => {
            let models = [];

            let proceed = () => {
                this.main('views/merge', {
                    models: models,
                    scope: this.name,
                    collection: options.collection
                });
            };

            let i = 0;

            ids.forEach(id => {
                let current = model.clone();

                current.id = id;
                models.push(current);

                this.listenToOnce(current, 'sync', () => {
                    i++;

                    if (i === ids.length) {
                        proceed();
                    }
                });

                current.fetch();
            });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionRelated(options) {
        let id = options.id;
        let link = options.link;

        let viewName = this.getViewName('listRelated');

        let model;

        this.getModel()
            .then(m => {
                model = m;
                model.id = id;

                return model.fetch({main: true});
            })
            .then(() => {
                let foreignEntityType = model.getLinkParam(link, 'entity');

                if (!foreignEntityType) {
                    this.baseController.error404();

                    throw new Error(`Bad link '${link}'.`);
                }

                return this.collectionFactory.create(foreignEntityType);
            })
            .then(collection => {
                collection.url = model.entityType + '/' + id + '/' + link;

                this.main(viewName, {
                    scope: this.name,
                    model: model,
                    collection: collection,
                    link: link,
                });
            })
    }

    /**
     * Get a collection for the current controller.
     *
     * @protected
     * @param {boolean} [usePreviouslyFetched=false] Use a previously fetched. @todo Revise.
     * @return {Promise<module:collection>}
     */
    getCollection(usePreviouslyFetched) {
        if (!this.name) {
            throw new Error('No collection for unnamed controller');
        }

        let entityType = this.entityType || this.name;

        if (usePreviouslyFetched && entityType in this.collectionMap) {
            let collection = this.collectionMap[entityType];

            return Promise.resolve(collection);
        }

        return this.collectionFactory.create(entityType, collection => {
            this.collectionMap[entityType] = collection;

            this.listenTo(collection, 'sync', () => collection.isFetched = true);
        });
    }

    /**
     * Get a model for the current controller.
     *
     * @protected
     * @param {Function} [callback]
     * @param {Object} [context]
     * @return {Promise<module:model>}
     */
    getModel(callback, context) {
        context = context || this;

        if (!this.name) {
            throw new Error('No collection for unnamed controller');
        }

        let modelName = this.entityType || this.name;

        return this.modelFactory.create(modelName, model => {
            if (callback) {
                callback.call(context, model);
            }
        });
    }
}

export default RecordController;
PK]�L����layout-manager.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module layout-manager */

import {Events} from 'bullbone';

/**
 * A layout manager.
 *
 * @mixes Bull.Events
 */
class LayoutManager {

    /**
     * @param {module:cache|null} [cache] A cache.
     * @param {string} [applicationId] An application ID.
     * @param {string} [userId] A user ID.
     */
    constructor(cache, applicationId, userId) {

        /**
         * @private
         * @type {module:cache|null}
         */
        this.cache = cache || null;

        /**
         * @private
         * @type {string}
         */
        this.applicationId = applicationId || 'default-id';

        /**
         * @private
         * @type {string|null}
         */
        this.userId = userId || null;

        /**
         * @private
         * @type {Object}
         */
        this.data = {};

        /** @private */
        this.ajax = Espo.Ajax;
    }

    /**
     * Set a user ID. To be used for the cache purpose.
     *
     * @param {string} userId A user ID.
     *
     * @todo Throw an exception if already set.
     */
    setUserId(userId) {
        this.userId = userId
    }

    /**
     * @private
     * @param {string} scope
     * @param {string} type
     * @returns {string}
     */
    getKey(scope, type) {
        if (this.userId) {
            return this.applicationId + '-' + this.userId + '-' + scope + '-' + type;
        }

        return this.applicationId + '-' + scope + '-' + type;
    }

    /**
     * @private
     * @param {string} scope
     * @param {string} type
     * @param {string} [setId]
     * @returns {string}
     */
    getUrl(scope, type, setId) {
        let url = scope + '/layout/' + type;

        if (setId) {
            url += '/' + setId;
        }

        return url;
    }

    /**
     * @callback module:layout-manager~getCallback
     *
     * @param {*} layout A layout.
     */

    /**
     * Get a layout.
     *
     * @param {string} scope A scope (entity type).
     * @param {string} type A layout type (name).
     * @param {module:layout-manager~getCallback} callback
     * @param {boolean} [cache=true] Use cache.
     */
    get(scope, type, callback, cache) {
        if (typeof cache === 'undefined') {
            cache = true;
        }

        let key = this.getKey(scope, type);

        if (cache) {
            if (key in this.data) {
                if (typeof callback === 'function') {
                    callback(this.data[key]);
                }

                return;
            }
        }

        if (this.cache && cache) {
            let cached = this.cache.get('app-layout', key);

            if (cached) {
                if (typeof callback === 'function') {
                    callback(cached);
                }

                this.data[key] = cached;

                return;
            }
        }

        this.ajax
            .getRequest(this.getUrl(scope, type))
            .then(
                layout => {
                    if (typeof callback === 'function') {
                        callback(layout);
                    }

                    this.data[key] = layout;

                    if (this.cache) {
                        this.cache.set('app-layout', key, layout);
                    }
                }
            );
    }

    /**
     * Get an original layout.
     *
     * @param {string} scope A scope (entity type).
     * @param {string} type A layout type (name).
     * @param {string} [setId]
     * @param {module:layout-manager~getCallback} callback
     */
    getOriginal(scope, type, setId, callback) {
        let url = 'Layout/action/getOriginal?scope='+scope+'&name='+type;

        if (setId) {
            url += '&setId=' + setId;
        }

        Espo.Ajax
            .getRequest(url)
            .then(
                layout => {
                    if (typeof callback === 'function') {
                        callback(layout);
                    }
                }
            );
    }

    /**
     * Store and set a layout.
     *
     * @param {string} scope A scope (entity type).
     * @param {string} type A type (name).
     * @param {*} layout A layout.
     * @param {Function} callback A callback.
     * @param {string} [setId] A set ID.
     * @returns {Promise}
     */
    set(scope, type, layout, callback, setId) {
        return Espo.Ajax
            .putRequest(this.getUrl(scope, type, setId), layout)
            .then(
                () => {
                    let key = this.getKey(scope, type);

                    if (this.cache && key) {
                        this.cache.clear('app-layout', key);
                    }

                    delete this.data[key];

                    this.trigger('sync');

                    if (typeof callback === 'function') {
                        callback();
                    }
                }
            );
    }

    /**
     * Reset a layout to default.
     *
     * @param {string} scope A scope (entity type).
     * @param {string} type A type (name).
     * @param {Function} callback A callback.
     * @param {string} [setId] A set ID.
     */
    resetToDefault(scope, type, callback, setId) {
        Espo.Ajax
            .postRequest('Layout/action/resetToDefault', {
                scope: scope,
                name: type,
                setId: setId,
            })
            .then(
                () => {
                    let key = this.getKey(scope, type);

                    if (this.cache) {
                        this.cache.clear('app-layout', key);
                    }

                    delete this.data[key];

                    this.trigger('sync');

                    if (typeof callback === 'function') {
                        callback();
                    }
                }
            );
    }

    /**
     * Clear loaded data.
     */
    clearLoadedData() {
        this.data = {};
    }
}

Object.assign(LayoutManager.prototype, Events);

export default LayoutManager;
PK]�c ŝ6�6dynamic-logic.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module dynamic-logic */

/**
 * Dynamic logic. Handles form appearance and behaviour depending on conditions.
 *
 * @internal Instantiated in advanced-pack.
 */
class DynamicLogic {

    /**
     * @param {Object} defs Definitions.
     * @param {module:views/record/base} recordView A record view.
     */
    constructor(defs, recordView) {

        /**
         * @type {Object} Definitions.
         * @private
         */
        this.defs = defs || {};

        /**
         *
         * @type {module:views/record/base}
         * @private
         */
        this.recordView = recordView;

        /**
         * @type {string[]}
         * @private
         */
        this.fieldTypeList = ['visible', 'required', 'readOnly'];

        /**
         * @type {string[]}
         * @private
         */
        this.panelTypeList = ['visible', 'styled'];
    }

    /**
     * Process.
     */
    process() {
        let fields = this.defs.fields || {};

        Object.keys(fields).forEach(field => {
            var item = (fields[field] || {});

            this.fieldTypeList.forEach(type => {
                if (!(type in item)) {
                    return;
                }

                if (!item[type]) {
                    return;
                }

                let typeItem = (item[type] || {});

                if (!typeItem.conditionGroup) {
                    return;
                }

                let result = this.checkConditionGroup(typeItem.conditionGroup);

                let methodName;

                if (result) {
                    methodName = 'makeField' + Espo.Utils.upperCaseFirst(type) + 'True';
                }
                else {
                    methodName = 'makeField' + Espo.Utils.upperCaseFirst(type) + 'False';
                }

                this[methodName](field);
            });
        });

        let panels = this.defs.panels || {};

        Object.keys(panels).forEach(panel => {
            this.panelTypeList.forEach(type => {
                this.processPanel(panel, type);
            });
        });

        let options = this.defs.options || {};

        Object.keys(options).forEach(field => {
            let itemList = options[field];

            if (!options[field]) {
                return;
            }

            let isMet = false;

            for (let i in itemList) {
                let item = itemList[i];

                if (this.checkConditionGroup(item.conditionGroup)) {
                    this.setOptionList(field, item.optionList || []);

                    isMet = true;

                    break;
                }
            }

            if (!isMet) {
                this.resetOptionList(field);
            }
        });
    }

    /**
     * @param {string} panel A panel name.
     * @param {string} type A type.
     * @private
     */
    processPanel(panel, type) {
        let panels = this.defs.panels || {};
        let item = (panels[panel] || {});

        if (!(type in item)) {
            return;
        }

        let typeItem = (item[type] || {});

        if (!typeItem.conditionGroup) {
            return;
        }

        let result = this.checkConditionGroup(typeItem.conditionGroup);

        let methodName;

        if (result) {
            methodName = 'makePanel' + Espo.Utils.upperCaseFirst(type) + 'True';
        }
        else {
            methodName = 'makePanel' + Espo.Utils.upperCaseFirst(type) + 'False';
        }

        this[methodName](panel);
    }

    /**
     * Check a condition group.
     * @param {Object} data A condition group.
     * @param {'and'|'or'|'not'} [type='and'] A type.
     * @returns {boolean}
     */
    checkConditionGroup(data, type) {
        type = type || 'and';

        let list;
        let result = false;

        if (type === 'and') {
            list =  data || [];

            result = true;

            for (let i in list) {
                if (!this.checkCondition(list[i])) {
                    result = false;

                    break;
                }
            }
        }
        else if (type === 'or') {
            list =  data || [];

            for (let i in list) {
                if (this.checkCondition(list[i])) {
                    result = true;

                    break;
                }
            }
        }
        else if (type === 'not') {
            if (data) {
                result = !this.checkCondition(data);
            }
        }

        return result;
    }

    /**
     * Check a condition.
     *
     * @param {Object} defs Definitions.
     * @returns {boolean}
     */
    checkCondition(defs) {
        defs = defs || {};

        let type = defs.type || 'equals';

        if (['or', 'and', 'not'].includes(type)) {
            return this.checkConditionGroup(defs.value, /** @type {'or'|'and'|'not'} */ type);
        }

        let attribute = defs.attribute;
        let value = defs.value;

        if (!attribute) {
            return false;
        }

        var setValue = this.recordView.model.get(attribute);

        if (type === 'equals') {
            if (!value) {
                return false;
            }

            return setValue === value;
        }

        if (type === 'notEquals') {
            if (!value) {
                return false;
            }

            return setValue !== value;
        }

        if (type === 'isEmpty') {
            if (Array.isArray(setValue)) {
                return !setValue.length;
            }

            return setValue === null || (setValue === '') || typeof setValue === 'undefined';
        }

        if (type === 'isNotEmpty') {
            if (Array.isArray(setValue)) {
                return !!setValue.length;
            }

            return setValue !== null && (setValue !== '') && typeof setValue !== 'undefined';
        }

        if (type === 'isTrue') {
            return !!setValue;
        }

        if (type === 'isFalse') {
            return !setValue;
        }

        if (type === 'contains' || type === 'has') {
            if (!setValue) {
                return false;
            }

            return !!~setValue.indexOf(value);
        }

        if (type === 'notContains' || type === 'notHas') {
            if (!setValue) {
                return true;
            }

            return !~setValue.indexOf(value);
        }

        if (type === 'startsWith') {
            if (!setValue) {
                return false;
            }

            return setValue.indexOf(value) === 0;
        }

        if (type === 'endsWith') {
            if (!setValue) {
                return false;
            }

            return setValue.indexOf(value) === setValue.length - value.length;
        }

        if (type === 'matches') {
            if (!setValue) {
                return false;
            }

            let match = /^\/(.*)\/([a-z]*)$/.exec(value);

            if (!match || match.length < 2) {
                return false;
            }

            return (new RegExp(match[1], match[2])).test(setValue);
        }

        if (type === 'greaterThan') {
            return setValue > value;
        }

        if (type === 'lessThan') {
            return setValue < value;
        }

        if (type === 'greaterThanOrEquals') {
            return setValue >= value;
        }

        if (type === 'lessThanOrEquals') {
            return setValue <= value;
        }

        if (type === 'in') {
            return !!~value.indexOf(setValue);
        }

        if (type === 'notIn') {
            return !~value.indexOf(setValue);
        }

        if (type === 'isToday') {
            let dateTime = this.recordView.getDateTime();

            if (!setValue) {
                return false;
            }

            if (setValue.length > 10) {
                return dateTime.toMoment(setValue).isSame(dateTime.getNowMoment(), 'day');
            }

            return dateTime.toMomentDate(setValue).isSame(dateTime.getNowMoment(), 'day');
        }

        if (type === 'inFuture') {
            let dateTime = this.recordView.getDateTime();

            if (!setValue) {
                return false;
            }

            if (setValue.length > 10) {
                return dateTime.toMoment(setValue).isAfter(dateTime.getNowMoment(), 'day');
            }

            return dateTime.toMomentDate(setValue).isAfter(dateTime.getNowMoment(), 'day');
        }

        if (type === 'inPast') {
            let dateTime = this.recordView.getDateTime();

            if (!setValue) {
                return false;
            }


            if (setValue.length > 10) {
                return dateTime.toMoment(setValue).isBefore(dateTime.getNowMoment(), 'day');
            }

            return dateTime.toMomentDate(setValue).isBefore(dateTime.getNowMoment(), 'day');
        }

        return false;
    }

    /**
     * @param {string} field
     * @param {string[]} optionList
     * @private
     */
    setOptionList(field, optionList) {
        this.recordView.setFieldOptionList(field, optionList);
    }

    /**
     * @param {string} field
     * @private
     */
    resetOptionList(field) {
        this.recordView.resetFieldOptionList(field);
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * @param {string} field
     * @private
     */
    makeFieldVisibleTrue(field) {
        this.recordView.showField(field);
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * @param {string} field
     * @private
     */
    makeFieldVisibleFalse(field) {
        this.recordView.hideField(field);
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * @param {string} field
     * @private
     */
    makeFieldRequiredTrue(field) {
        this.recordView.setFieldRequired(field);
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * @param {string} field
     * @private
     */
    makeFieldRequiredFalse(field) {
        this.recordView.setFieldNotRequired(field);
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * @param {string} field
     * @private
     */
    makeFieldReadOnlyTrue(field) {
        this.recordView.setFieldReadOnly(field);
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * @param {string} field
     * @private
     */
    makeFieldReadOnlyFalse(field) {
        this.recordView.setFieldNotReadOnly(field);
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * @param {string} panel
     * @private
     */
    makePanelVisibleTrue(panel) {
        this.recordView.showPanel(panel, 'dynamicLogic');
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * @param {string} panel
     * @private
     */
    makePanelVisibleFalse(panel) {
        this.recordView.hidePanel(panel, false, 'dynamicLogic');
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * @param {string} panel
     * @private
     */
    makePanelStyledTrue(panel) {
        this.recordView.stylePanel(panel);
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * @param {string} panel
     * @private
     */
    makePanelStyledFalse(panel) {
        this.recordView.unstylePanel(panel);
    }

    /**
     * Add a panel-visible condition.
     *
     * @param {string} name A panel name.
     * @param {Object} item Condition definitions.
     */
    addPanelVisibleCondition(name, item) {
        this.defs.panels = this.defs.panels || {};
        this.defs.panels[name] = this.defs.panels[name] || {};

        this.defs.panels[name].visible = item;

        this.processPanel(name, 'visible');
    }

    /**
     * Add a panel-styled condition.
     *
     * @param {string} name A panel name.
     * @param {Object} item Condition definitions.
     */
    addPanelStyledCondition(name, item) {
        this.defs.panels = this.defs.panels || {};
        this.defs.panels[name] = this.defs.panels[name] || {};

        this.defs.panels[name].styled = item;

        this.processPanel(name, 'styled');
    }
}

export default DynamicLogic;
PK]��'�<	<	broadcast-channel.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module broadcast-channel */

class BroadcastChannel {

    constructor() {
        this.object = null;

        if (window.BroadcastChannel) {
            this.object = new window.BroadcastChannel('app');
        }
    }

    /**
     * Post a message.
     *
     * @param {string} message A message.
     */
    postMessage(message) {
        if (!this.object) {
            return;
        }

        this.object.postMessage(message);
    }

    /**
     * @callback module:broadcast-channel~callback
     *
     * @param {MessageEvent} event An event. A message can be obtained from the `data` property.
     */

    /**
     * Subscribe to a message.
     *
     * @param {module:broadcast-channel~callback} callback A callback.
     */
    subscribe(callback) {
        if (!this.object) {
            return;
        }

        this.object.addEventListener('message', callback);
    }
}

export default BroadcastChannel;
PK]R�+handlers/import.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ActionHandler from 'action-handler';

class ImportHandler extends ActionHandler {

    // noinspection JSUnusedGlobalSymbols
    actionErrorExport() {
        Espo.Ajax
            .postRequest(`Import/${this.view.model.id}/exportErrors`)
            .then(data => {
                if (!data.attachmentId) {
                    let message = this.view.translate('noErrors', 'messages', 'Import');

                    Espo.Ui.warning(message);

                    return;
                }

                window.location = this.view.getBasePath() + '?entryPoint=download&id=' + data.attachmentId;
            });
    }
}

export default ImportHandler;
PK]�
�̙�handlers/email-filter.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import DynamicHandler from 'dynamic-handler';

class EmailFilterHandler extends DynamicHandler {

    init() {
        if (this.model.isNew()) {
            if (!this.recordView.getUser().isAdmin()) {
                this.recordView.hideField('isGlobal');
            }
        }

        if (
            !this.model.isNew() &&
            !this.recordView.getUser().isAdmin() &&
            !this.model.get('isGlobal')
        ) {
            this.recordView.hideField('isGlobal');
        }

        if (this.model.isNew() && !this.model.get('parentId')) {
            this.model.set('parentType', 'User');
            this.model.set('parentId', this.recordView.getUser().id);
            this.model.set('parentName', this.recordView.getUser().get('name'));

            if (!this.recordView.getUser().isAdmin()) {
                this.recordView.setFieldReadOnly('parent');
            }
        }
        else if (
            this.model.get('parentType') &&
            !this.recordView.options.duplicateSourceId
        ) {
            this.recordView.setFieldReadOnly('parent');
            this.recordView.setFieldReadOnly('isGlobal');
        }

        this.recordView.listenTo(this.model, 'change:isGlobal', (model, value, o) => {
            if (!o.ui) {
                return;
            }

            if (value) {
                this.model.set({
                    action: 'Skip',
                    parentName: null,
                    parentType: null,
                    parentId: null,
                    emailFolderId: null,
                    groupEmailFolderId: null,
                    markAsRead: false,
                });
            }
        });

        this.recordView.listenTo(this.model, 'change:parentType', (model, value, o) => {
            if (!o.ui) {
                return;
            }

            // Avoiding side effects.
            setTimeout(() => {
                if (value !== 'User') {
                    this.model.set('markAsRead', false);
                }

                if (value === 'EmailAccount') {
                    this.model.set('action', 'Skip');
                    this.model.set('emailFolderId', null);
                    this.model.set('groupEmailFolderId', null);
                    this.model.set('markAsRead', false);

                    return;
                }

                if (value !== 'InboundEmail') {
                    if (this.model.get('action') === 'Move to Group Folder') {
                        this.model.set('action', 'Skip');
                    }

                    this.model.set('groupEmailFolderId', null);

                    return;
                }

                if (value !== 'User') {
                    if (this.model.get('action') === 'Move to Folder') {
                        this.model.set('action', 'Skip');
                    }

                    this.model.set('groupFolderId', null);
                }
            }, 40);
        });
    }
}

export default EmailFilterHandler;

PK]�-d(		handlers/login.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module handlers/login */

/**
 * Custom login handling. To be extended.
 *
 * @abstract
 */
class LoginHandler {

    /**
     * @param {module:views/login} loginView A login view.
     * @param {Object.<string, *>} data Additional metadata.
     */
    constructor(loginView, data) {
        /**
         * A login view.
         * @protected
         * @type {module:views/login}
         */
        this.loginView = loginView;

        /**
         * Additional metadata.
         * @protected
         * @type {Object.<string, *>}
         */
        this.data = data;
    }

    /**
     * Process. Called on 'Sign in' button click.
     *
     * @public
     * @abstract
     * @return {Promise<Object.<string, string>>} Resolved with headers to be sent to the `App/user` endpoint.
     */
    process() {
        return Promise.resolve({});
    }
}

export default LoginHandler;
PK]\�??handlers/login/oidc.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import LoginHandler from 'handlers/login';
import Base64 from 'js-base64';

class OidcLoginHandler extends LoginHandler {

    /** @inheritDoc */
    process() {
        Espo.Ui.notify(' ... ');

        return new Promise((resolve, reject) => {
            Espo.Ajax.getRequest('Oidc/authorizationData')
                .then(data => {
                    Espo.Ui.notify(false);

                    this.processWithData(data)
                        .then(info => {
                            let code = info.code;
                            let nonce = info.nonce;

                            let authString = Base64.encode('**oidc:' + code);

                            let headers = {
                                'Espo-Authorization': authString,
                                'Authorization': 'Basic ' + authString,
                                'X-Oidc-Authorization-Nonce': nonce,
                            };

                            resolve(headers);
                        })
                        .catch(() => {
                            reject();
                        });
                })
                .catch(() => {
                    Espo.Ui.notify(false)

                    reject();
                });
        });
    }

    /**
     * @private
     * @param {{
     *  endpoint: string,
     *  clientId: string,
     *  redirectUri: string,
     *  scopes: string[],
     *  claims: ?string,
     *  prompt: 'login'|'consent'|'select_account',
     *  maxAge: ?Number,
     * }} data
     * @return {Promise<{code: string, nonce: string}>}
     */
    processWithData(data) {
        let state = (Math.random() + 1).toString(36).substring(7);
        let nonce = (Math.random() + 1).toString(36).substring(7);

        let params = {
            client_id: data.clientId,
            redirect_uri: data.redirectUri,
            response_type: 'code',
            scope: data.scopes.join(' '),
            state: state,
            nonce: nonce,
            prompt: data.prompt,
        };

        if (data.maxAge || data.maxAge === 0) {
            params.max_age = data.maxAge;
        }

        if (data.claims) {
            params.claims = data.claims;
        }

        let partList = Object.entries(params)
            .map(([key, value]) => {
                return key + '=' + encodeURIComponent(value);
            });

        let url = data.endpoint + '?' + partList.join('&');

        return this.processWindow(url, state, nonce);
    }

    /**
     * @private
     * @param {string} url
     * @param {string} state
     * @param {string} nonce
     * @return {Promise<{code: string, nonce: string}>}
     */
    processWindow(url, state, nonce) {
        let proxy = window.open(url, 'ConnectWithOAuth', 'location=0,status=0,width=800,height=800');

        return new Promise((resolve, reject) => {
            let fail = () => {
                window.clearInterval(interval);

                if (!proxy.closed) {
                    proxy.close();
                }

                reject();
            };

            let interval = window.setInterval(() => {
                if (proxy.closed) {
                    fail();

                    return;
                }

                let url;

                try {
                    url = proxy.location.href;
                }
                catch (e) {
                    return;
                }

                if (!url) {
                    return;
                }

                let parsedData = this.parseWindowUrl(url);

                if (!parsedData) {
                    fail();
                    Espo.Ui.error('Could not parse URL', true);

                    return;
                }

                if ((parsedData.error || parsedData.code) && parsedData.state !== state) {
                    fail();
                    Espo.Ui.error('State mismatch', true);

                    return;
                }

                if (parsedData.error) {
                    fail();
                    Espo.Ui.error(parsedData.errorDescription || this.loginView.translate('Error'), true);

                    return;
                }

                if (parsedData.code) {
                    window.clearInterval(interval);
                    proxy.close();

                    resolve({
                        code: parsedData.code,
                        nonce: nonce,
                    });
                }
            }, 300);
        });
    }

    /**
     * @param {string} url
     * @return {?{
     *     code: ?string,
     *     state: ?string,
     *     error: ?string,
     *     errorDescription: ?string,
     * }}
     */
    parseWindowUrl(url) {
        try {
            let params = new URL(url).searchParams;

            return {
                code: params.get('code'),
                state: params.get('state'),
                error: params.get('error'),
                errorDescription: params.get('errorDescription'),
            };
        }
        catch(e) {
            return null;
        }
    }
}

export default OidcLoginHandler;
PK]!����handlers/create-related.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/**
 * Prepares attributes for a related record that is being created.
 *
 * @abstract
 */
class CreateRelatedHandler {

    /**
     * @param {module:view-helper} viewHelper
     */
    constructor(viewHelper) {
        // noinspection JSUnusedGlobalSymbols
        this.viewHelper = viewHelper;
    }

    /**
     * Get attributes for a new record.
     *
     * @abstract
     * @param {module:model} model A model.
     * @return {Promise<Object.<string, *>>} Attributes.
     */
    getAttributes(model) {
        return Promise.resolve({});
    }
}

export default CreateRelatedHandler;
PK]�y$

'handlers/select-related/same-account.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import SelectRelatedHandler from 'handlers/select-related';

class SameAccountSelectRelatedHandler extends SelectRelatedHandler {

    /**
     * @param {module:model} model
     * @return {Promise<module:handlers/select-related~filters>}
     */
    getFilters(model) {
        let advanced = {};

        let accountId = null;
        let accountName = null;

        if (model.get('accountId')) {
            accountId = model.get('accountId');
            accountName = model.get('accountName');
        }

        if (!accountId && model.get('parentType') === 'Account' && model.get('parentId')) {
            accountId = model.get('parentId');
            accountName = model.get('parentName');
        }

        if (accountId) {
            advanced.account = {
                attribute: 'accountId',
                type: 'equals',
                value: accountId,
                data: {
                    type: 'is',
                    nameValue: accountName,
                },
            };
        }

        return Promise.resolve({
            advanced: advanced,
        });
    }
}

export default SameAccountSelectRelatedHandler;
PK]����L
L
,handlers/select-related/same-account-many.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import SelectRelatedHandler from 'handlers/select-related';

class SameAccountManySelectRelatedHandler extends SelectRelatedHandler {

    /**
     * @param {module:model} model
     * @return {Promise<module:handlers/select-related~filters>}
     */
    getFilters(model) {
        let advanced = {};

        let accountId = null;
        let accountName = null;

        if (model.get('accountId')) {
            accountId = model.get('accountId');
            accountName = model.get('accountName');
        }

        if (!accountId && model.get('parentType') === 'Account' && model.get('parentId')) {
            accountId = model.get('parentId');
            accountName = model.get('parentName');
        }

        if (accountId) {
            let nameHash = {};
            nameHash[accountId] = accountName;

            advanced.accounts = {
                field: 'accounts',
                type: 'linkedWith',
                value: [accountId],
                data: {nameHash: nameHash},
            };
        }

        return Promise.resolve({
            advanced: advanced,
        });
    }
}

// noinspection JSUnusedGlobalSymbols
export default SameAccountManySelectRelatedHandler;
PK]D9ˡ]	]	handlers/select-related.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module handlers/select-related */

/**
 * @typedef Object
 * @name module:handlers/select-related~filters
 * @property {Object.<string, module:search-manager~advancedFilter>} [advanced]
 *  Advanced filters map. A field name as a key.
 * @property {string[]} [bool] Bool filters.
 * @property {string} [primary] A primary filter.
 */

/**
 * Prepares filters for selecting records to relate.
 *
 * @abstract
 */
class SelectRelatedHandler {

    /**
     * @param {module:view-helper} viewHelper
     */
    constructor(viewHelper) {
        // noinspection JSUnusedGlobalSymbols
        /** @protected */
        this.viewHelper = viewHelper;
    }

    /**
     * Get filters for selecting records to relate.
     *
     * @abstract
     * @param {module:model} model A model.
     * @return {Promise<module:handlers/select-related~filters>} Filters.
     */
    getFilters(model) {
        return Promise.resolve({});
    }
}

export default SelectRelatedHandler;
PK]��,��handlers/working-time-range.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import {Events} from 'bullbone';

/**
 * @mixes Bull.Events
 */
class WorkingTimeRangeHandler {

    constructor(view) {
        /** @type {module:views/record/edit} */
        this.view = view;
    }

    process() {
        this.listenTo(this.view.model, 'change:dateStart', (model, value, o) => {
            if (!o.ui || model.get('dateEnd')) {
                return;
            }

            setTimeout(() => model.set('dateEnd', value), 50);
        });
    }
}

Object.assign(WorkingTimeRangeHandler.prototype, Events);

export default WorkingTimeRangeHandler;
PK]��mm

collections/tree.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module collections/tree */

import Collection from 'collection';

class TreeCollection extends Collection {

    createSeed() {
        let seed = new this.constructor();

        seed.url = this.url;
        seed.model = this.model;
        seed.name = this.name;
        seed.entityType = this.entityType;
        seed.defs = this.defs;

        return seed;
    }

    prepareAttributes(response, options) {
        let list = super.prepareAttributes(response, options);

        let seed = this.clone();

        seed.reset();

        this.path = response.path;
        /**
         * @type {{
         *     name: string,
         *     upperId?: string,
         *     upperName?: string,
         * }|null}
         */
        this.categoryData = response.data || null;

        let f = (l, depth) => {
            l.forEach(d => {
                d.depth = depth;

                let c = this.createSeed();

                if (d.childList) {
                    if (d.childList.length) {
                        f(d.childList, depth + 1);
                        c.set(d.childList);
                        d.childCollection = c;

                        return;
                    }

                    d.childCollection = c;

                    return;
                }

                if (d.childList === null) {
                    d.childCollection = null;

                    return;
                }

                d.childCollection = c;
            });
        };

        f(list, 0);

        return list;
    }

    fetch(options) {
        options = options || {};
        options.data = options.data || {};

        if (this.parentId) {
            options.data.parentId = this.parentId;
        }

        options.data.maxDepth = this.maxDepth;

        return super.fetch(options);
    }
}

export default TreeCollection;
PK]Ya_n

collections/note.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module collections/note */

import Collection from 'collection';

class NoteCollection extends Collection {

    /** @inheritDoc */
    prepareAttributes(response, params) {
        let total = this.total;

        let list = super.prepareAttributes(response, params);

        if (params.data && params.data.after) {
            if (total >= 0 && response.total >= 0) {
                this.total = total + response.total;
            } else {
                this.total = total;
            }
        }

        return list;
    }

    /**
     * Fetch new records.
     *
     * @param {Object} [options] Options.
     * @returns {Promise}
     */
    fetchNew(options) {
        options = options || {};

        options.data = options.data || {};
        options.fetchNew = true;
        options.noRebuild = true;
        options.lengthBeforeFetch = this.length;

        if (this.length) {
            options.data.after = this.models[0].get('createdAt');
            options.remove = false;
            options.at = 0;
            options.maxSize = null;
        }

        return this.fetch(options);
    }
}

export default NoteCollection;
PK]�++view.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module view */

import {View as BullView} from 'bullbone';

/**
 * A base view. All views should extend this class.
 *
 * @see {@link https://docs.espocrm.com/development/view/}
 * @mixes Bull.Events
 */
class View extends BullView {

    /**
     * @callback module:view~actionHandlerCallback
     * @param {MouseEvent} event A DOM event.
     * @param {HTMLElement} element A target element.
     */

    /**
     * A model.
     *
     * @name model
     * @type {module:model|null}
     * @memberOf View.prototype
     * @public
     */

    /**
     * A collection.
     *
     * @name collection
     * @type {module:collection|null}
     * @memberOf View.prototype
     * @public
     */

    /**
     * A helper.
     *
     * @name _helper
     * @type {module:view-helper}
     * @memberOf View.prototype
     * @private
     */

    /**
     * When the view is ready. Can be useful to prevent race condition when re-initialization is needed
     * in-between initialization and render.
     *
     * @return Promise
     * @todo Move to Bull.View.
     */
    whenReady() {
        if (this.isReady) {
            return Promise.resolve();
        }

        return new Promise(resolve => {
            this.once('ready', () => resolve());
        });
    }

    /**
     * Add a DOM click event handler for a target defined by `data-action="{name}"` attribute.
     *
     * @param {string} action An action name.
     * @param {module:view~actionHandlerCallback} handler A handler.
     */
    addActionHandler(action, handler) {
        const fullAction = `click [data-action="${action}"]`;

        this.events[fullAction] = e => {
            handler.call(this, e.originalEvent, e.currentTarget);
        };
    }

    /**
     * Escape a string.
     *
     * @param {string} string
     * @returns {string}
     */
    escapeString(string) {
        return Handlebars.Utils.escapeExpression(string);
    }

    /**
     * Show a notify-message.
     *
     * @deprecated Use `Espo.Ui.notify`.
     * @param {string|false} label
     * @param {string} [type]
     * @param {number} [timeout]
     * @param {string} [scope]
     */
    notify(label, type, timeout, scope) {
        if (!label) {
            Espo.Ui.notify(false);

            return;
        }

        scope = scope || null;
        timeout = timeout || 2000;

        if (!type) {
            timeout = void 0;
        }

        let text = this.getLanguage().translate(label, 'labels', scope);

        Espo.Ui.notify(text, type, timeout);
    }

    /**
     * Get a view-helper.
     *
     * @returns {module:view-helper}
     */
    getHelper() {
        return this._helper;
    }

    /**
     * Get a current user.
     *
     * @returns {module:models/user}
     */
    getUser() {
        return this._helper.user;
    }

    /**
     * Get the preferences.
     *
     * @returns {module:models/preferences}
     */
    getPreferences() {
        return this._helper.preferences;
    }

    /**
     * Get the config.
     *
     * @returns {module:models/settings}
     */
    getConfig() {
        return this._helper.settings;
    }

    /**
     * Get the ACL.
     *
     * @returns {module:acl-manager}
     */
    getAcl() {
        return this._helper.acl;
    }

    /**
     * Get the model factory.
     *
     * @returns {module:model-factory}
     */
    getModelFactory() {
        return this._helper.modelFactory;
    }

    /**
     * Get the collection factory.
     *
     * @returns {module:collection-factory}
     */
    getCollectionFactory() {
        return this._helper.collectionFactory;
    }

    /**
     * Get the router.
     *
     * @returns {module:router}
     */
    getRouter() {
        return this._helper.router;
    }

    /**
     * Get the storage-util.
     *
     * @returns {module:storage}
     */
    getStorage() {
        return this._helper.storage;
    }

    /**
     * Get the session-storage-util.
     *
     * @returns {module:session-storage}
     */
    getSessionStorage() {
        return this._helper.sessionStorage;
    }

    /**
     * Get the language-util.
     *
     * @returns {module:language}
     */
    getLanguage() {
        return this._helper.language;
    }

    /**
     * Get metadata.
     *
     * @returns {module:metadata}
     */
    getMetadata() {
        return this._helper.metadata;
    }

    /**
     * Get the cache-util.
     *
     * @returns {module:cache}
     */
    getCache() {
        return this._helper.cache;
    }

    /**
     * Get the date-time util.
     *
     * @returns {module:date-time}
     */
    getDateTime() {
        return this._helper.dateTime;
    }

    /**
     * Get the number-util.
     *
     * @returns {module:num-util}
     */
    getNumberUtil() {
        return this._helper.numberUtil;
    }

    /**
     * Get the field manager.
     *
     * @returns {module:field-manager}
     */
    getFieldManager() {
        return this._helper.fieldManager;
    }

    /**
     * Get the base-controller.
     *
     * @returns {module:controllers/base}
     */
    getBaseController() {
        return this._helper.baseController;
    }

    /**
     * Get the theme manager.
     *
     * @returns {module:theme-manager}
     */
    getThemeManager() {
        return this._helper.themeManager;
    }

    /**
     * Update a page title. Supposed to be overridden if needed.
     */
    updatePageTitle() {
        var title = this.getConfig().get('applicationName') || 'EspoCRM';

        this.setPageTitle(title);
    }

    /**
     * Set a page title.
     *
     * @param {string} title A title.
     */
    setPageTitle(title) {
        this.getHelper().pageTitle.setTitle(title);
    }

    /**
     * Translate a label.
     *
     * @param {string} label Label.
     * @param {string} [category] Category.
     * @param {string} [scope] Scope.
     * @returns {string}
     */
    translate(label, category, scope) {
        return this.getLanguage().translate(label, category, scope);
    }

    /**
     * Get a base path.
     *
     * @returns {string}
     */
    getBasePath() {
        return this._helper.basePath || '';
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * Ajax request.
     *
     * @deprecated Use `Espo.Ajax`.
     * @todo Remove in v9.0.
     * @param {string} url An URL.
     * @param {string} type A method.
     * @param {any} [data] Data.
     * @param {Object} [options] Options.
     * @returns {Promise<*>}
     */
    ajaxRequest(url, type, data, options) {
        return /** @type {Promise<*>} */ Espo.Ajax.request(url, type, data, options);
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * POST request.
     *
     * @deprecated Use `Espo.Ajax.postRequest`.
     * @todo Remove in v9.0.
     * @param {string} url An URL.
     * @param {any} [data] Data.
     * @param {Object} [options] Options.
     * @returns {Promise<any>}
     */
    ajaxPostRequest(url, data, options) {
        return Espo.Ajax.postRequest(url, data, options);
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * GET request.
     *
     * @deprecated Use `Espo.Ajax.getRequest`.
     * @todo Remove in v9.0.
     * @param {string} url An URL.
     * @param {any} [data] Data.
     * @param {Object} [options] Options.
     * @returns {Promise<any>}
     */
    ajaxGetRequest(url, data, options) {
        return Espo.Ajax.getRequest(url, data, options);
    }

    /**
     * @typedef {Object} module:view~ConfirmOptions
     *
     * @property {string} message A message.
     * @property {string} [confirmText] A confirm-button text.
     * @property {string} [cancelText] A cancel-button text.
     * @property {'danger'|'success'|'warning'|'default'} [confirmStyle='danger'] A confirm-button style.
     * @property {'static'|boolean} [backdrop=false] A backdrop.
     * @property {function():void} [cancelCallback] A cancel-callback.
     */

    /**
     * Show a confirmation dialog.
     *
     * @param {string|module:view~ConfirmOptions} o A message or options.
     * @param [callback] A callback. Deprecated, use a promise.
     * @param [context] A context. Deprecated.
     * @returns {Promise} To be resolved if confirmed.
     */
    confirm(o, callback, context) {
        let message;

        if (typeof o === 'string' || o instanceof String) {
            message = o;

            o = /** @type {module:view~ConfirmOptions} */{};
        }
        else {
            o = o || {};

            message = o.message;
        }

        if (message) {
            message = this.getHelper()
                .transformMarkdownText(message, {linksInNewTab: true})
                .toString();
        }

        let confirmText = o.confirmText || this.translate('Yes');
        let confirmStyle = o.confirmStyle || null;
        let cancelText = o.cancelText || this.translate('Cancel');

        return Espo.Ui.confirm(message, {
            confirmText: confirmText,
            cancelText: cancelText,
            confirmStyle: confirmStyle,
            backdrop: ('backdrop' in o) ? o.backdrop : true,
            isHtml: true,
        }, callback, context);
    }
}

export default View;
PK]A��v�"�"ui/multi-select.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module module:ui/multi-select */

import Selectize from 'lib!selectize';

/**
 * @typedef module:ui/multi-select~Options
 * @type {Object}
 * @property {{value: string, text: string}[]} items
 * @property {string} [delimiter=':,:']
 * @property {boolean} [restoreOnBackspace=false]
 * @property {boolean} [removeButton=true]
 * @property {boolean} [draggable=false]
 * @property {boolean} [selectOnTab=false]
 * @property {boolean} [matchAnyWord=false]
 * @property {boolean} [allowCustomOptions=false]
 * @property {function (string): {value: string, text: string}|null} [create]
 */

/**
 * @module ui/multi-select
 */
const MultiSelect = {
    /**
     * @param {Element|JQuery} element An element.
     * @param {module:ui/multi-select~Options} options Options.
     */
    init: function (element, options) {
        let $el = $(element);

        options = MultiSelect.applyDefaultOptions(options);

        let plugins = [];

        if (options.removeButton) {
            plugins.push('remove_button');
        }

        if (options.draggable) {
            plugins.push('drag_drop');
        }

        if (options.restoreOnBackspace) {
            MultiSelect.loadRestoreOnBackspacePlugin();
            plugins.push('restore_on_backspace_espo')
        }

        MultiSelect.loadBypassCtrlEnterPlugin();
        plugins.push('bypass_ctrl_enter');

        let selectizeOptions = {
            options: options.items,
            plugins: plugins,
            delimiter: options.delimiter,
            labelField: 'text',
            valueField: 'value',
            searchField: ['text'],
            highlight: false,
            selectOnTab: options.selectOnTab,
        };

        if (!options.matchAnyWord) {
            // noinspection JSUnresolvedReference
            /** @this Selectize */
            selectizeOptions.score = function (search) {
                // noinspection JSUnresolvedReference
                let score = this.getScoreFunction(search);

                search = search.toLowerCase();

                return function (item) {
                    if (item.text.toLowerCase().indexOf(search) === 0) {
                        return score(item);
                    }

                    return 0;
                };
            };
        }

        if (options.matchAnyWord) {
            /** @this Selectize */
            selectizeOptions.score = function (search) {
                // noinspection JSUnresolvedReference
                let score = this.getScoreFunction(search);

                search = search.toLowerCase();

                return function (item) {
                    let text = item.text.toLowerCase();

                    if (
                        !text.split(' ').find(item => item.startsWith(search)) &&
                        !text.startsWith(search)
                    ) {
                        return 0;
                    }

                    return score(item);
                };
            };
        }

        if (options.allowCustomOptions) {
            selectizeOptions.persist = false;
            selectizeOptions.create = options.create;
            // noinspection JSUnusedGlobalSymbols
            selectizeOptions.render = {
                option_create: data => {
                    return $('<div>')
                        .addClass('create')
                        .append(
                            $('<span>')
                                .text(data.input)
                                .addClass('text-bold')
                        )
                        .append('&hellip;')
                        .get(0).outerHTML;
                },
            };
        }

        $el.selectize(selectizeOptions);
    },

    /**
     * Focus.
     *
     * @param {Element|JQuery} element An element.
     */
    focus: function (element) {
        let $el = $(element);

        if (
            !$el[0] ||
            !$el[0].selectize
        ) {
            return;
        }

        let selectize = $el[0].selectize;

        selectize.focus();
    },

    /**
     * @private
     * @param {module:ui/multi-select~Options} options
     * @return {module:ui/multi-select~Options}
     */
    applyDefaultOptions: function (options) {
        options = Espo.Utils.clone(options);

        let defaults = {
            removeButton: true,
            draggable: false,
            selectOnTab: false,
            delimiter: ':,:',
            matchAnyWord: false,
            allowCustomOptions: false,
        };

        for (let key in defaults) {
            if (key in options) {
                continue;
            }

            options[key] = defaults[key];
        }

        return options;
    },

    /**
     * @private
     */
    loadBypassCtrlEnterPlugin: function () {
        if ('bypass_ctrl_enter' in Selectize.plugins) {
            return;
        }

        const IS_MAC = /Mac/.test(navigator.userAgent);

        Selectize.define('bypass_ctrl_enter', function () {
            let self = this;

            this.onKeyDown = (function() {
                let original = self.onKeyDown;

                return function (e) {
                    if (e.code === 'Enter' && (IS_MAC ? e.metaKey : e.ctrlKey)) {
                        return;
                    }

                    return original.apply(this, arguments);
                };
            })();
        });
    },

    /**
     * @private
     */
    loadRestoreOnBackspacePlugin: function () {
        if ('restore_on_backspace_espo' in Selectize.plugins) {
            return;
        }

        Selectize.define('restore_on_backspace_espo', function (options) {
            options.text = options.text || function (option) {
                return option[this.settings.labelField];
            };

            let self = this;

            this.onKeyDown = (function() {
                let original = self.onKeyDown;

                return function (e) {
                    let index, option;

                    if (
                        e.code === 'Backspace' &&
                        this.$control_input.val() === '' &&
                        !this.$activeItems.length
                    ) {
                        index = this.caretPos - 1;

                        if (index >= 0 && index < this.items.length) {
                            option = this.options[this.items[index]];

                            option = {
                                value: option.value,
                                $order: option.$order,
                                text: option.value,
                            };

                            // noinspection JSUnresolvedReference
                            if (this.deleteSelection(e)) {
                                // noinspection JSUnresolvedReference
                                this.setTextboxValue(options.text.apply(this, [option]));
                                this.refreshOptions(true);
                            }

                            e.preventDefault();

                            return;
                        }
                    }

                    return original.apply(this, arguments);
                };
            })();
        });
    },
};

export default MultiSelect;
PK]%[�C4O4Oui/select.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module module:ui/select */

import Selectize from 'lib!selectize';

/**
 * @typedef module:ui/select~Options
 * @type {Object}
 * @property {boolean} [selectOnTab=false] To select on tab.
 * @property {boolean} [matchAnyWord=false] To match any word when searching.
 * @property {function(string, module:ui/select~OptionItemsCallback): void} [load] Loads additional items
 *   when typing in search.
 * @property {function(string, module:ui/select~OptionItemFull): Number} [score] A score function scoring
 *   searched items.
 * @property {'value'|'text'|'$order'|'$score'} [sortBy='$order'] Item sorting.
 * @property {'asc'|'desc'} [sortDirection='asc'] Sort direction.
 */

/**
 * @callback  module:ui/select~OptionItemsCallback
 * @param {module:ui/select~OptionItem[]} list An option item list.
 */

/**
 * @typedef module:ui/select~OptionItem
 * @type {Object}
 * @property {string} value A value.
 * @property {string} text A label.
 */

/**
 * @typedef module:ui/select~OptionItemFull
 * @type {Object}
 * @property {string} value A value.
 * @property {string} text A label.
 * @property {Number} $order An order index.
 */

/**
 * @module ui/select
 *
 * Important. The Selectize library is heavily customized to fix multitude of UIX issues.
 * Upgrading is not advisable. Consider forking.
 */
const Select = {
    /**
     * @param {Element|JQuery} element An element.
     * @param {module:ui/select~Options} [options] Options.
     */
    init: function (element, options = {}) {
        const score = options.score;
        const $el = $(element);

        options = Select.applyDefaultOptions(options || {});

        const plugins = [];

        Select.loadEspoSelectPlugin();

        plugins.push('espo_select');

        const itemClasses = {};

        const allowedValues = $el.children().toArray().map(item => {
            const value = item.getAttributeNode('value').value;

            if (item.classList) {
                itemClasses[value] = item.classList.toString();
            }

            return value;
        });

        let $relativeParent = null;

        const $modalBody = $el.closest('.modal-body');

        if ($modalBody.length) {
            $relativeParent = $modalBody;
        }

        const selectizeOptions = {
            sortField: [{field: options.sortBy, direction: options.sortDirection}],
            load: options.load,
            loadThrottle: 1,
            plugins: plugins,
            highlight: false,
            selectOnTab: options.selectOnTab,
            copyClassesToDropdown: false,
            allowEmptyOption: allowedValues.includes(''),
            showEmptyOptionInDropdown: true,
            $relativeParent: $relativeParent,
            render: {
                item: function (data) {
                    return $('<div>')
                        .addClass('item')
                        .addClass(itemClasses[data.value] || '')
                        .text(data.text)
                        .get(0).outerHTML;
                },
                option: function (data) {
                    const $div = $('<div>')
                        .addClass('option')
                        .addClass(data.value === '' ? 'selectize-dropdown-emptyoptionlabel' : '')
                        .addClass(itemClasses[data.value] || '')
                        .val(data.value)
                        .text(data.text);

                    if (data.text === '') {
                        $div.html('&nbsp;');
                    }

                    return $div.get(0).outerHTML;
                },
            },
            onDelete: function (values) {
                while (values.length) {
                    this.removeItem(values.pop(), true);
                }

                this.showInput();
                this.positionDropdown();
                this.refreshOptions(true);
            },
        };

        if (!options.matchAnyWord) {
            /** @this Selectize */
            selectizeOptions.score = function (search) {
                const score = this.getScoreFunction(search);

                search = search.toLowerCase();

                return function (item) {
                    if (item.text.toLowerCase().indexOf(search) === 0) {
                        return score(item);
                    }

                    return 0;
                };
            };
        }

        if (options.matchAnyWord) {
            /** @this Selectize */
            selectizeOptions.score = function (search) {
                const score = this.getScoreFunction(search);

                search = search.toLowerCase();

                return function (item) {
                    const text = item.text.toLowerCase();

                    if (
                        !text.split(' ').find(item => item.startsWith(search)) &&
                        !text.startsWith(search)
                    ) {
                        return 0;
                    }

                    return score(item);
                };
            };
        }

        if (options.score) {

            selectizeOptions.score = function (search) {
                return function (item) {
                    return score(search, item);
                };
            };
        }

        $el.selectize(selectizeOptions);
    },

    /**
     * Focus.
     *
     * @param {Element|JQuery} element An element.
     * @param {{noTrigger?: boolean}} [options] Options.
     */
    focus: function (element, options) {
        const $el = $(element);

        options = options || {};

        if (
            !$el[0] ||
            !$el[0].selectize
        ) {
            return;
        }

        const selectize = $el[0].selectize;

        if (options.noTrigger) {
            selectize.focusNoTrigger = true;
        }

        selectize.focus();

        if (options.noTrigger) {
            setTimeout(() => selectize.focusNoTrigger = false, 100);
        }
    },

    /**
     * Set options.
     *
     * @param {Element|JQuery} element An element.
     * @param {{value: string, text: string}[]} options Options.
     */
    setOptions: function (element, options) {
        const $el = $(element);

        const selectize = $el.get(0).selectize;

        selectize.clearOptions(true);
        selectize.load(callback => {
            callback(
                options.map(item => {
                    return {
                        value: item.value,
                        text: item.text || item.label,
                    };
                })
            );
        });
    },

    /**
     * Set value.
     *
     * @param {JQuery} $el An element.
     * @param {string} value A value.
     */
    setValue: function ($el, value) {
        const selectize = $el.get(0).selectize;

        selectize.setValue(value, true);
    },

    /**
     * Destroy.
     *
     * @param {JQuery} $el An element.
     */
    destroy: function ($el) {
        if (!$el.length || !$el[0].selectize) {
            return;
        }

        $el[0].selectize.destroy();
    },

    /**
     * @private
     * @param {module:ui/select~Options} options
     * @return {module:ui/select~Options}
     */
    applyDefaultOptions: function (options) {
        options = Espo.Utils.clone(options);

        const defaults = {
            selectOnTab: false,
            matchAnyWord: false,
            sortBy: '$order',
            sortDirection: 'asc',
        };

        for (const key in defaults) {
            if (key in options) {
                continue;
            }

            options[key] = defaults[key];
        }

        return options;
    },

    /**
     * @private
     */
    loadEspoSelectPlugin: function () {
        if ('espo_select' in Selectize.plugins) {
            return;
        }

        const IS_MAC = /Mac/.test(navigator.userAgent);
        const KEY_BACKSPACE = 8;

        Selectize.define('espo_select', function () {
            const self = this;

            this.setup = (function () {
                const original = self.setup;

                return function () {
                    original.apply(this, arguments);

                    self.selectedValue = self.items[0];

                    self.$dropdown
                        .on('mouseup', '[data-selectable]', function () {
                            $(document).off('mouseup.select');

                            return self.onOptionSelect.apply(self, arguments);
                        });

                    self.$dropdown
                        .on('mousedown', '[data-selectable]', function () {
                            // Prevent issue when down inside, up outside.
                            $(document).one('mouseup.select', function () {
                                self.focusOnControlSilently();
                            });
                        });


                    self.$control_input.css({'width': '4px'});
                };
            })();

            this.focusOnControlSilently = function () {
                self.preventReOpenOnFocus = true;
                self.$control_input[0].focus();
                self.preventReOpenOnFocus = false;
            };

            /*this.positionDropdown = (function () {
                let original = self.positionDropdown;

                return function () {
                    original.apply(this, arguments);

                    this.$dropdown.css({margin: 'unset'});
                };
            })();*/

            this.refreshOptions = (function () {
                const original = self.refreshOptions;

                return function () {
                    if (self.focusNoTrigger) {
                        original.apply(this, [false]);
                        return;
                    }

                    original.apply(this, arguments);
                };
            })();

            this.blur = (function () {
                const original = self.blur;

                return function () {
                    // Prevent closing on mouse down.
                    if (self.preventClose) {
                        return;
                    }

                    original.apply(this, arguments);
                };
            })();

            this.close = (function () {
                const original = self.close;

                return function () {
                    if (self.preventClose) {
                        return;
                    }

                    original.apply(this, arguments);
                };
            })();

            this.onOptionSelect = (function () {
                const original = self.onOptionSelect;

                return function (e) {
                    if (e.type === 'mousedown' || e.type === 'click') {
                        self.preventClose = true;
                        setTimeout(() => self.preventClose = false, 100);

                        return;
                    }

                    self.preventClose = false;

                    if (e.type === 'mouseup') {
                        setTimeout(() => self.focusOnControlSilently(), 50);
                    }

                    original.apply(this, arguments);

                    self.selectedValue = $(e.currentTarget).attr('data-value');
                };
            })();

            this.open = (function() {
                const original = self.open;

                return function () {
                    const toProcess = !(self.isLocked || self.isOpen);

                    original.apply(this, arguments);

                    if (!toProcess) {
                        return;
                    }

                    const $dropdownContent = self.$dropdown.children().first();
                    const $selected = $dropdownContent.find('.selected');

                    if (!$selected.length) {
                        return;
                    }

                    let scrollTo = $selected.get(0).offsetTop - $dropdownContent.get(0).clientHeight;
                    scrollTo = scrollTo >= 0 ? scrollTo : 0;

                    $dropdownContent
                        .find('.selectize-dropdown-content')
                        .scrollTop(scrollTo);
                };
            })();

            this.onMouseDown = (function() {
                const original = self.onMouseDown;

                return function (e) {
                    // Prevent flicking when clicking on input.
                    if (!self.isOpen && !self.isInputHidden && self.$control_input.val()) {
                        return;
                    }

                    if (self.isOpen) {
                        self.closedByMouseDown = true;
                    }

                    return original.apply(this, arguments);
                };
            })();

            this.onFocus = (function() {
                const original = self.onFocus;

                return function (e) {
                    if (self.preventReOpenOnFocus) {
                        return;
                    }

                    if (self.closedByMouseDown) {
                        self.closedByMouseDown = false;

                        return;
                    }

                    self.closedByMouseDown = false;

                    return original.apply(this, arguments);
                };
            })();

            this.restoreSelectedValue = function () {
                if (this.preventRevertLoop) {
                    return;
                }

                this.preventRevertLoop = true;
                setTimeout(() => this.preventRevertLoop = false, 10);

                this.setValue(this.selectedValue, true);
            };

            this.onBlur = (function() {
                const original = self.onBlur;

                return function () {
                    // Prevent closing on mouse down.
                    if (self.preventClose) {
                        return;
                    }

                    self.restoreSelectedValue();

                    self.$control_input.css({width: '4px'});

                    return original.apply(this, arguments);
                };
            })();

            this.onKeyDown = (function() {
                const original = self.onKeyDown;

                return function (e) {
                    if (IS_MAC ? e.metaKey : e.ctrlKey) {
                        if (!self.items.length) {
                            self.restoreSelectedValue();
                            self.focus();
                        }

                        return;
                    }

                    if (e.code === 'Escape') {
                        if (self.isOpen || !self.isInputHidden) {
                            e.stopPropagation();
                        }

                        if (self.isOpen) {
                            self.close();
                        }

                        if (!self.isInputHidden) {
                            self.hideInput();
                        }

                        self.addItem(this.selectedValue, true);
                    }

                    if (self.isFull() || self.isInputHidden) {
                        if (
                            e.key.length === 1 &&
                            (
                                e.code.match(/Key[A-Z]/i) ||
                                e.key.match(/[0-9]/) ||
                                RegExp(/^\p{L}/, 'u').test(e.key) // is letter
                            )
                        ) {
                            const keyCode = e.keyCode;
                            e.keyCode = KEY_BACKSPACE;
                            self.deleteSelection(e);
                            e.keyCode = keyCode;

                            self.$control_input.width(15);
                        }
                    }

                    return original.apply(this, arguments);
                };
            })();

            this.positionDropdown = (function() {
                const POSITION = {
                    top: 'top',
                    bottom: 'bottom',
                };

                return function() {
                    const $control = self.$control;

                    const offset = this.settings.dropdownParent === 'body' ?
                        $control.offset() :
                        $control.position();

                    offset.top += $control.outerHeight(true);

                    const dropdownHeight = self.$dropdown.prop('scrollHeight') + 5;
                    const controlPosTop = self.$control.get(0).getBoundingClientRect().top;
                    const wrapperHeight = self.$wrapper.height();

                    const controlPosBottom = self.$control.get(0).getBoundingClientRect().bottom;

                    const boundaryTop = !this.settings.$relativeParent ? 0 :
                        this.settings.$relativeParent.get(0).getBoundingClientRect().top;

                    const position =
                        controlPosTop + dropdownHeight + wrapperHeight > window.innerHeight &&
                        controlPosBottom - dropdownHeight - wrapperHeight >= boundaryTop ?
                            POSITION.top :
                            POSITION.bottom;

                    const styles = {
                        width: $control.outerWidth(),
                        left: offset.left,
                    };

                    if (position === POSITION.top) {
                        Object.assign(styles, {
                            bottom: offset.top,
                            top: 'unset',
                            margin: '0 0 0 0',
                        });

                        self.$dropdown.addClass('selectize-position-top');
                    } else {
                        Object.assign(styles, {
                            top: offset.top,
                            bottom: 'unset',
                            margin: '0 0 0 0',
                        });

                        self.$dropdown.removeClass('selectize-position-top');
                    }

                    self.$dropdown.css(styles);
                }
            })();
        });
    },
};

export default Select;
PK]N�Τ'/'/date-time.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module date-time */

import moment from 'moment';

/**
 * A date-time util.
 */
class DateTime {

    constructor() {}

    /**
     * A system date format.
     *
     * @type {string}
     */
    internalDateFormat = 'YYYY-MM-DD'

    /**
     * A system date-time format.
     *
     * @type {string}
     */
    internalDateTimeFormat = 'YYYY-MM-DD HH:mm'

    /**
     * A system date-time format including seconds.
     *
     * @type {string}
     */
    internalDateTimeFullFormat = 'YYYY-MM-DD HH:mm:ss'

    /**
     * A date format for a current user.
     *
     * @type {string}
     */
    dateFormat = 'MM/DD/YYYY'

    /**
     * A time format for a current user.
     *
     * @type {string}
     */
    timeFormat = 'HH:mm'

    /**
     * A time zone for a current user.
     *
     * @type {string|null}
     */
    timeZone = null

    /**
     * A week start for a current user.
     *
     * @type {Number}
     */
    weekStart = 1

    /** @private */
    readableDateFormatMap = {
        'DD.MM.YYYY': 'DD MMM',
        'DD/MM/YYYY': 'DD MMM',
    }

    /** @private */
    readableShortDateFormatMap = {
        'DD.MM.YYYY': 'D MMM',
        'DD/MM/YYYY': 'D MMM',
    }

    /**
     * Whether a time format has a meridian (am/pm).
     *
     * @returns {boolean}
     */
    hasMeridian() {
        return (new RegExp('A', 'i')).test(this.timeFormat);
    }

    /**
     * Get a date format.
     *
     * @returns {string}
     */
    getDateFormat() {
        return this.dateFormat;
    }

    /**
     * Get a time format.
     *
     * @returns {string}
     */
    getTimeFormat() {
        return this.timeFormat;
    }

    /**
     * Get a date-time format.
     *
     * @returns {string}
     */
    getDateTimeFormat() {
        return this.dateFormat + ' ' + this.timeFormat;
    }

    /**
     * Get a readable date format.
     *
     * @returns {string}
     */
    getReadableDateFormat() {
        return this.readableDateFormatMap[this.getDateFormat()] || 'MMM DD';
    }

    /**
     * Get a readable short date format.
     *
     * @returns {string}
     */
    getReadableShortDateFormat() {
        return this.readableShortDateFormatMap[this.getDateFormat()] || 'MMM D';
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * Get a readable date-time format.
     *
     * @returns {string}
     */
    getReadableDateTimeFormat() {
        return this.getReadableDateFormat() + ' ' + this.timeFormat;
    }

    /**
     * Get a readable short date-time format.
     *
     * @returns {string}
     */
    getReadableShortDateTimeFormat() {
        return this.getReadableShortDateFormat() + ' ' + this.timeFormat;
    }

    /**
     * Convert a date from a display representation to system.
     *
     * @param {string} string A date value.
     * @returns {string|-1} A system date value.
     */
    fromDisplayDate(string) {
        let m = moment(string, this.dateFormat);

        if (!m.isValid()) {
            return -1;
        }

        return m.format(this.internalDateFormat);
    }

    /**
     * Get a time-zone.
     *
     * @returns {string}
     */
    getTimeZone() {
        return this.timeZone ? this.timeZone : 'UTC';
    }

    /**
     * Convert a date from system to a display representation.
     *
     * @param {string} string A system date value.
     * @returns {string} A display date value.
     */
    toDisplayDate(string) {
        if (!string || (typeof string !== 'string')) {
            return '';
        }

        let m = moment(string, this.internalDateFormat);

        if (!m.isValid()) {
            return '';
        }

        return m.format(this.dateFormat);
    }

    /**
     * Convert a date-time from system to a display representation.
     *
     * @param {string} string A system date-time value.
     * @returns {string|-1} A display date-time value.
     */
    fromDisplay(string) {
        let m;

        if (this.timeZone) {
            m = moment.tz(string, this.getDateTimeFormat(), this.timeZone).utc();
        }
        else {
            m = moment.utc(string, this.getDateTimeFormat());
        }

        if (!m.isValid()) {
            return -1;
        }

        return m.format(this.internalDateTimeFormat) + ':00';
    }

    /**
     * Convert a date-time from system to a display representation.
     *
     * @param {string} string A system date value.
     * @returns {string} A display date-time value.
     */
    toDisplay(string) {
        if (!string) {
            return '';
        }

        return this.toMoment(string).format(this.getDateTimeFormat());
    }

    /**
     * Get a now moment.
     *
     * @returns {moment.Moment}
     */
    getNowMoment() {
        return moment().tz(this.getTimeZone())
    }

    /**
     * Convert a date to a moment.
     *
     * @param {string} string A date value in a system representation.
     * @returns {moment.Moment}
     */
    toMomentDate(string) {
        return moment.utc(string, this.internalDateFormat);
    }

    /**
     * Convert a date-time to a moment.
     *
     * @param {string} string A date-time value in a system representation.
     * @returns {moment.Moment}
     */
    toMoment(string) {
        let m = moment.utc(string, this.internalDateTimeFullFormat);

        if (this.timeZone) {
            // noinspection JSUnresolvedReference
            m = m.tz(this.timeZone);
        }

        return m;
    }

    /**
     * Convert a date-time value from ISO to a system representation.
     *
     * @param {string} string
     * @returns {string} A date-time value in a system representation.
     */
    fromIso(string) {
        if (!string) {
            return '';
        }

        let m = moment(string).utc();

        return m.format(this.internalDateTimeFormat);
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * Convert a date-time value from system to an ISO representation.
     *
     * @param string A date-time value in a system representation.
     * @returns {string} An ISO date-time value.
     */
    toIso(string) {
        return this.toMoment(string).format();
    }

    /**
     * Get a today date value in a system representation.
     *
     * @returns {string}
     */
    getToday() {
        return moment().tz(this.getTimeZone()).format(this.internalDateFormat);
    }

    /**
     * Get a date-time value in a system representation, shifted from now.
     *
     * @param {Number} shift A number to shift by.
     * @param {'minutes'|'hours'|'days'|'weeks'|'months'|'years'} type A shift unit.
     * @param {Number} [multiplicity] A number of minutes a value will be aliquot to.
     * @returns {string} A date-time value in a system representation
     */
    getDateTimeShiftedFromNow(shift, type, multiplicity) {
        if (!multiplicity) {
            return moment.utc().add(shift, type).format(this.internalDateTimeFormat);
        }

        let unix = moment().unix();

        unix = unix - (unix % (multiplicity * 60));

        return moment.unix(unix).utc().add(shift, type).format(this.internalDateTimeFormat);
    }

    /**
     * Get a date value in a system representation, shifted from today.
     *
     * @param {Number} shift A number to shift by.
     * @param {'days'|'weeks'|'months'|'years'} type A shift unit.
     * @returns {string} A date value in a system representation
     */
    getDateShiftedFromToday(shift, type) {
        return moment.tz(this.getTimeZone()).add(shift, type).format(this.internalDateFormat);
    }

    /**
     * Get a now date-time value in a system representation.
     *
     * @param {Number} [multiplicity] A number of minutes a value will be aliquot to.
     * @returns {string}
     */
    getNow(multiplicity) {
        if (!multiplicity) {
            return moment.utc().format(this.internalDateTimeFormat);
        }

        let unix = moment().unix();

        unix = unix - (unix % (multiplicity * 60));

        return moment.unix(unix).utc().format(this.internalDateTimeFormat);
    }

    /**
     * Set settings and preferences.
     *
     * @param {module:models/settings} settings Settings.
     * @param {module:models/preferences} preferences Preferences.
     * @internal
     */
    setSettingsAndPreferences(settings, preferences) {
        if (settings.has('dateFormat')) {
            this.dateFormat = settings.get('dateFormat');
        }

        if (settings.has('timeFormat')) {
            this.timeFormat = settings.get('timeFormat');
        }

        if (settings.has('timeZone')) {
            this.timeZone = settings.get('timeZone') || null;

            if (this.timeZone === 'UTC') {
                this.timeZone = null;
            }
        }

        if (settings.has('weekStart')) {
            this.weekStart = settings.get('weekStart');
        }

        preferences.on('change', model => {
            if (model.has('dateFormat') && model.get('dateFormat')) {
                this.dateFormat = model.get('dateFormat');
            }

            if (model.has('timeFormat') && model.get('timeFormat')) {
                this.timeFormat = model.get('timeFormat');
            }

            if (model.has('timeZone') && model.get('timeZone')) {

                this.timeZone = model.get('timeZone');
            }

            if (model.has('weekStart') && model.get('weekStart') !== -1) {
                this.weekStart = model.get('weekStart');
            }

            if (this.timeZone === 'UTC') {
                this.timeZone = null;
            }
        });
    }

    /**
     * Set a language.
     *
     * @param {module:language} language A language.
     * @internal
     */
    setLanguage(language) {
        moment.updateLocale('en', {
            months: language.translatePath(['Global', 'lists', 'monthNames']),
            monthsShort: language.translatePath(['Global', 'lists', 'monthNamesShort']),
            weekdays: language.translatePath(['Global', 'lists', 'dayNames']),
            weekdaysShort: language.translatePath(['Global', 'lists', 'dayNamesShort']),
            weekdaysMin: language.translatePath(['Global', 'lists', 'dayNamesMin']),
        });

        moment.locale('en');
    }
}

export default DateTime;
PK]����views/address-map/view.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/address-map/view', ['views/main'], function (Dep) {

    return Dep.extend({

        templateContent: `
            <div class="header page-header">{{{header}}}</div>
            <div class="map-container">{{{map}}}</div>
        `,

        setup: function () {
            this.scope = this.model.entityType;

            this.createView('header', 'views/header', {
                model: this.model,
                fullSelector: '#main > .header',
                scope: this.model.entityType,
                fontSizeFlexible: true,
            });
        },

        afterRender: function () {
        	var field = this.options.field;

            var viewName = this.model.getFieldParam(field + 'Map', 'view') ||
                this.getFieldManager().getViewName('map');

            this.createView('map', viewName, {
                model: this.model,
                name: field + 'Map',
                selector: '.map-container',
                height: this.getHelper().calculateContentContainerHeight(this.$el.find('.map-container')),
            }, (view) => {
            	view.render();
            });
        },

        getHeader: function () {
            let name = this.model.get('name');

            if (!name) {
                name = this.model.id;
            }

            let recordUrl = '#' + this.model.entityType + '/view/' + this.model.id
            let scopeLabel = this.getLanguage().translate(this.model.entityType, 'scopeNamesPlural');
            let fieldLabel = this.translate(this.options.field, 'fields', this.model.entityType);
            let rootUrl = this.options.rootUrl ||
                this.options.params.rootUrl ||
                '#' + this.model.entityType;

            let $name = $('<a>')
                .attr('href', recordUrl)
                .append(
                    $('<span>')
                    .addClass('font-size-flexible title')
                    .text(name)
                );

            if (this.model.get('deleted')) {
                $name.css('text-decoration', 'line-through');
            }

            let $root = $('<span>')
                .append(
                    $('<a>')
                        .attr('href', rootUrl)
                        .addClass('action')
                        .attr('data-action', 'navigateToRoot')
                        .text(scopeLabel)
                );

            let headerIconHtml = this.getHeaderIconHtml();

            if (headerIconHtml) {
                $root.prepend(headerIconHtml);
            }

            let $field = $('<span>').text(fieldLabel)

            return this.buildHeaderHtml([
                $root,
                $name,
                $field,
            ]);
        },
    });
});
PK]��w�SS(views/working-time-range/fields/users.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/working-time-range/fields/users', ['views/fields/link-multiple'], function (Dep) {

    return Dep.extend({

        getSelectPrimaryFilterName: function () {
            return 'active';
        },
    });
});
PK]:�{R	R	+views/working-time-range/fields/date-end.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/working-time-range/fields/date-end', ['views/fields/date'], function (Dep) {


    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            this.validations.push('afterOrSame');
        },

        validateAfterOrSame: function () {
            let field = 'dateStart';

            let value = this.model.get(this.name);
            let otherValue = this.model.get(field);

            if (value && otherValue) {
                if (moment(value).unix() < moment(otherValue).unix()) {
                    let msg = this.translate('fieldShouldAfter', 'messages')
                        .replace('{field}', this.getLabelText())
                        .replace('{otherField}', this.translate(field, 'fields', this.model.entityType));

                    this.showValidationMessage(msg);

                    return true;
                }
            }

            return false;
        },
    });
});
PK]�鱽�&�&views/export/record/record.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/export/record/record', ['views/record/edit-for-modal'], function (Dep) {

    /**
     * @class
     * @name Class
     * @memberOf module:views/export/record/record
     * @extends module:views/record/edit-for-modal
     */
    return Dep.extend(/** @lends module:views/export/record/record.Class# */{

        /**
         * @type {string[]},
         */
        formatList: null,

        /**
         * @type {Object.<string, string[]>},
         */
        customParams: null,

        setup: function () {
            Dep.prototype.setup.call(this);
        },

        setupBeforeFinal: function () {
            this.formatList = this.options.formatList;
            this.scope = this.options.scope;

            let fieldsData = this.getExportFieldsData();

            this.setupExportFieldDefs(fieldsData);
            this.setupExportLayout(fieldsData);
            this.setupExportDynamicLogic();

            this.controlFormatField();
            this.listenTo(this.model, 'change:format', () => this.controlFormatField());

            this.controlAllFields();
            this.listenTo(this.model, 'change:exportAllFields', () => this.controlAllFields());

            Dep.prototype.setupBeforeFinal.call(this);
        },

        setupExportFieldDefs: function (fieldsData) {
            let fieldDefs = {
                format: {
                    type: 'enum',
                    options: this.formatList,
                },
                fieldList: {
                    type: 'multiEnum',
                    options: fieldsData.list,
                    required: true,
                },
                exportAllFields: {
                    type: 'bool',
                },
            };

            this.customParams = {};

            this.formatList.forEach(format => {
                let fields = this.getFormatParamsDefs(format).fields || {};

                this.customParams[format] = [];

                for (let name in fields) {
                    let newName = this.modifyParamName(format, name);

                    this.customParams[format].push(name);

                    fieldDefs[newName] = Espo.Utils.cloneDeep(fields[name]);
                }
            });

            this.model.setDefs({fields: fieldDefs});
        },

        setupExportLayout: function (fieldsData) {
            this.detailLayout = [];

            let mainPanel = {
                rows: [
                    [
                        {name: 'format'},
                        false
                    ],
                    [
                        {name: 'exportAllFields'},
                        false
                    ],
                    [
                        {
                            name: 'fieldList',
                            options: {
                                translatedOptions: fieldsData.translations,
                            },
                        }
                    ],
                ]
            };

            this.detailLayout.push(mainPanel);

            this.formatList.forEach(format => {
                let rows = this.getFormatParamsDefs(format).layout || [];

                rows.forEach(row => {
                    row.forEach(item => {
                        item.name = this.modifyParamName(format, item.name);
                    });
                })

                this.detailLayout.push({
                    name: format,
                    rows: rows,
                })
            });
        },

        setupExportDynamicLogic: function () {
            this.dynamicLogicDefs = {
                fields: {},
            };

            this.formatList.forEach(format => {
                let defs = this.getFormatParamsDefs(format).dynamicLogic || {};

                this.customParams[format].forEach(param => {
                    let logic = defs[param] || {};

                    if (!logic.visible) {
                        logic.visible = {};
                    }

                    if (!logic.visible.conditionGroup) {
                        logic.visible.conditionGroup = [];
                    }

                    logic.visible.conditionGroup.push({
                        type: 'equals',
                        attribute: 'format',
                        value: format,
                    });

                    let newName = this.modifyParamName(format, param);

                    this.dynamicLogicDefs.fields[newName] = logic;
                });
            });
        },

        /**
         * @param {string} format
         * @return {string[]}
         */
        getFormatParamList: function (format) {
            return Object.keys(this.getFormatParamsDefs(format).fields || {});
        },

        /**
         * @private
         * @return {Object.<string, *>}
         */
        getFormatParamsDefs: function (format) {
            let defs = this.getMetadata().get(['app', 'export', 'formatDefs', format]) || {};

            return Espo.Utils.cloneDeep(defs.params || {});
        },

        /**
         * @param {string} format
         * @param {string} name
         * @return {string}
         */
        modifyParamName: function (format, name) {
            return format + Espo.Utils.upperCaseFirst(name);
        },

        /**
         * @return {{
         *   translations: Object.<string, string>,
         *   list: string[]
         * }}
         */
        getExportFieldsData: function () {
            let fieldList = this.getFieldManager().getEntityTypeFieldList(this.scope);
            let forbiddenFieldList = this.getAcl().getScopeForbiddenFieldList(this.scope);

            fieldList = fieldList.filter(item => {
                return !~forbiddenFieldList.indexOf(item);
            });

            fieldList = fieldList.filter(item => {
                let defs = this.getMetadata().get(['entityDefs', this.scope, 'fields', item]) || {};

                if (
                    defs.disabled ||
                    defs.exportDisabled ||
                    defs.type === 'map'
                ) {
                    return false
                }

                return true;
            });

            this.getLanguage().sortFieldList(this.scope, fieldList);

            fieldList.unshift('id');

            let fieldListTranslations = {};

            fieldList.forEach(item => {
                fieldListTranslations[item] = this.getLanguage().translate(item, 'fields', this.scope);
            });

            let setFieldList = this.model.get('fieldList') || [];

            setFieldList.forEach(item => {
                if (~fieldList.indexOf(item)) {
                    return;
                }

                if (!~item.indexOf('_')) {
                    return;
                }

                let arr = item.split('_');

                fieldList.push(item);

                let foreignScope = this.getMetadata().get(['entityDefs', this.scope, 'links', arr[0], 'entity']);

                if (!foreignScope) {
                    return;
                }

                fieldListTranslations[item] = this.getLanguage().translate(arr[0], 'links', this.scope) + '.' +
                    this.getLanguage().translate(arr[1], 'fields', foreignScope);
            });

            return {
                list: fieldList,
                translations: fieldListTranslations,
            };
        },

        controlAllFields: function () {
            if (!this.model.get('exportAllFields')) {
                this.showField('fieldList');

                return;
            }

            this.hideField('fieldList');
        },

        controlFormatField: function () {
            let format = this.model.get('format');

            this.formatList
                .filter(item => item !== format)
                .forEach(format => {
                    this.hidePanel(format);
                });

            this.formatList
                .filter(item => item === format)
                .forEach(format => {
                    this.customParams[format].length ?
                        this.showPanel(format) :
                        this.hidePanel(format);
                });
        },
    });
});
PK]�iw���views/export/modals/idle.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/export/modals/idle', ['views/modal', 'model'], function (Dep, Model) {

    return Dep.extend({

        className: 'dialog dialog-record',

        template: 'export/modals/idle',

        checkInterval: 4000,

        data: function () {
            return {
                infoText: this.translate('infoText', 'messages', 'Export'),
            };
        },

        events: {
            'click [data-action="download"]': function () {
                this.actionDownload();
            },
        },

        setup: function () {
            this.action = this.options.action;
            this.id = this.options.id;
            this.status = 'Pending';

            this.headerText = this.translate('Export');

            this.model = new Model();
            this.model.name = 'Export';

            this.model.setDefs({
                fields: {
                    'status': {
                        type: 'enum',
                        readOnly: true,
                        options: [
                            'Pending',
                            'Running',
                            'Success',
                            'Failed',
                        ],
                        style: {
                            'Success': 'success',
                            'Failed': 'danger',
                        },
                    },
                    'attachmentId': {
                        type: 'varchar',
                    },
                }
            });

            this.model.set({
                status: this.status,
                processedCount: null,
            });

            this.createView('record', 'views/record/edit-for-modal', {
                scope: 'None',
                model: this.model,
                selector: '.record',
                detailLayout: [
                    {
                        rows: [
                            [
                                {
                                    name: 'status',
                                    labelText: this.translate('status', 'fields', 'Export'),
                                }
                            ]
                        ]
                    }
                ],
            });

            this.on('close', () => {
                let status = this.model.get('status');

                if (
                    status !== 'Pending' &&
                    status !== 'Running'
                ) {
                    return;
                }

                Espo.Ajax.postRequest(`Export/${this.id}/subscribe`);
            });

            this.checkStatus();
        },

        checkStatus: function () {
            Espo.Ajax
                .getRequest(`Export/${this.id}/status`)
                .then(response => {
                    let status = response.status;

                    this.model.set('status', status);

                    if (status === 'Pending' || status === 'Running') {
                        setTimeout(() => this.checkStatus(), this.checkInterval);

                        return;
                    }

                    this.model.set({
                        attachmentId: response.attachmentId,
                    });

                    if (status === 'Success') {
                        this.trigger('success', {
                            attachmentId: response.attachmentId,
                        });

                        this.showDownload();
                    }

                    if (this.$el) {
                        this.$el.find('.info-text').addClass('hidden');
                    }
                });
        },

        showDownload: function () {
            this.$el.find('.download-container').removeClass('hidden');

            let $download = this.$el.find('[data-action="download"]');

            $download.removeClass('hidden');
        },

        actionDownload: function () {
            this.trigger('download', this.model.get('attachmentId'));

            this.close();
        },
    });
});
PK]�����views/export/modals/export.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/export/modals/export', ['views/modal', 'model'], function (Dep, Model) {

    return Dep.extend({

        cssName: 'export-modal',

        className: 'dialog dialog-record',

        template: 'export/modals/export',

        shortcutKeys: {
            'Control+Enter': 'export',
        },

        data: function () {
            return {};
        },

        setup: function () {
            this.buttonList = [
                {
                    name: 'export',
                    label: 'Export',
                    style: 'danger',
                    title: 'Ctrl+Enter',
                },
                {
                    name: 'cancel',
                    label: 'Cancel',
                }
            ];

            this.model = new Model();
            this.model.name = 'Export';

            this.scope = this.options.scope;

            if (this.options.fieldList) {
                const fieldList = this.options.fieldList
                    .filter(field => {
                        return !this.getMetadata()
                            .get(`entityDefs.${this.scope}.fields.${field}.exportDisabled`);
                    });

                this.model.set('fieldList', fieldList);
                this.model.set('exportAllFields', false);
            } else {
                this.model.set('exportAllFields', true);
            }

            let formatList =
                this.getMetadata().get(['scopes', this.scope, 'exportFormatList']) ||
                this.getMetadata().get('app.export.formatList');

            this.model.set('format', formatList[0]);

            this.createView('record', 'views/export/record/record', {
                scope: this.scope,
                model: this.model,
                selector: '.record',
                formatList: formatList,
            });
        },

        getRecordView: function () {
            return this.getView('record');
        },

        actionExport: function () {
            let recordView = this.getRecordView();

            let data = recordView.fetch();

            this.model.set(data);

            if (recordView.validate()) {
                return;
            }

            let returnData = {
                exportAllFields: data.exportAllFields,
                format: data.format,
            };

            if (!data.exportAllFields) {
                let attributeList = [];

                data.fieldList.forEach(item => {
                    if (item === 'id') {
                        attributeList.push('id');

                        return;
                    }

                    let type = this.getMetadata().get(['entityDefs', this.scope, 'fields', item, 'type']);

                    if (type) {
                        this.getFieldManager().getAttributeList(type, item)
                            .forEach(attribute => {
                                attributeList.push(attribute);
                            });
                    }

                    if (~item.indexOf('_')) {
                        attributeList.push(item);
                    }
                });

                returnData.attributeList = attributeList;
                returnData.fieldList = data.fieldList;
            }

            returnData.params = {};

            recordView.getFormatParamList(data.format).forEach(param => {
                let name = recordView.modifyParamName(data.format, param);

                let fieldView = recordView.getFieldView(name);

                if (!fieldView || fieldView.disabled) {
                    return;
                }

                this.getFieldManager()
                    .getActualAttributeList(fieldView.type, param)
                    .forEach(subParam => {
                        let name = recordView.modifyParamName(data.format, subParam);

                        returnData.params[subParam] = data[name];
                    });
            });

            this.trigger('proceed', returnData);
            this.close();
        },
    });
});
PK]��F��views/popup-notification.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';
import $ from 'jquery';

/**
 * To be extended with an own template.
 *
 * @abstract
 */
class PopupNotificationView extends View {

    type = 'default'
    style = 'default'
    closeButton = true
    soundPath = 'client/sounds/pop_cork'

    init() {
        super.init();

        let id = this.options.id;
        let containerSelector = this.containerSelector = '#' + id;

        this.setSelector(containerSelector);

        this.notificationSoundsDisabled = this.getConfig().get('notificationSoundsDisabled');

        this.soundPath = this.getBasePath() +
            (this.getConfig().get('popupNotificationSound') || this.soundPath);

        this.on('render', () => {
            $(containerSelector).remove();

            let className = 'popup-notification-' + Espo.Utils.toDom(this.type);

            $('<div>')
                .attr('id', id)
                .addClass('popup-notification')
                .addClass(className)
                .addClass('popup-notification-' + this.style)
                .appendTo('#popup-notifications-container');

            this.setElement(containerSelector);
        });

        this.on('after:render', () => {
            this.$el.find('[data-action="close"]').on('click', () =>{
                this.resolveCancel();
            });
        });

        this.once('after:render', () => {
            this.onShow();
        });

        this.once('remove', function () {
            $(containerSelector).remove();
        });

        this.notificationData = this.options.notificationData;
        this.notificationId = this.options.notificationId;
        this.id = this.options.id;
    }

    data() {
        return {
            closeButton: this.closeButton,
            notificationData: this.notificationData,
            notificationId: this.notificationId,
        };
    }

    playSound() {
        if (this.notificationSoundsDisabled) {
            return;
        }

        let html =
            '<audio autoplay="autoplay">' +
                '<source src="' + this.soundPath + '.mp3" type="audio/mpeg" />' +
                '<source src="' + this.soundPath + '.ogg" type="audio/ogg" />' +
                '<embed hidden="true" autostart="true" loop="false" src="' + this.soundPath +'.mp3" />' +
            '</audio>';

        let $audio = $(html);

        $audio.get(0).volume = 0.3;
        // noinspection JSUnresolvedReference
        $audio.get(0).play();
    }

    /**
     * @protected
     */
    onShow() {
        if (!this.options.isFirstCheck) {
            this.playSound();
        }
    }

    /**
     * An on-confirm action. To be extended.
     *
     * @protected
     */
    onConfirm() {}

    /**
     * An on-cancel action. To be extended.
     *
     * @protected
     */
    onCancel() {}

    resolveConfirm() {
        this.onConfirm();
        this.trigger('confirm');
        this.remove();
    }

    resolveCancel() {
        this.onCancel();
        this.trigger('cancel');
        this.remove();
    }

    // noinspection JSCheckFunctionSignatures
    /**
     * @deprecated Use `resolveConfirm`.
     */
    confirm() {
        console.warn(`Method 'confirm' in views/popup-notification is deprecated. Use 'resolveConfirm' instead.`);

        this.resolveConfirm();
    }

    /**
     * @deprecated Use `resolveCancel`.
     */
    cancel() {
        console.warn(`Method 'cancel' in views/popup-notification is deprecated. Use 'resolveCancel' instead.`);

        this.resolveCancel();
    }
}

export default PopupNotificationView;
PK]GG����views/list-tree.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ListView from 'views/list';

class ListTreeView extends ListView {

    searchPanel = false
    createButton = false

    name = 'listTree'

    getRecordViewName() {
        return this.getMetadata().get(['clientDefs', this.scope, 'recordViews', 'listTree']) ||
            'views/record/list-tree';
    }
}

export default ListTreeView
PK]�q�p	p	1views/lead-capture/opt-in-confirmation-success.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/lead-capture/opt-in-confirmation-success', ['view', 'model'], function (Dep, Model) {

    return Dep.extend({

        template: 'lead-capture/opt-in-confirmation-success',

        setup: function () {
            let model = new Model();

            this.resultData = this.options.resultData;

            if (this.resultData.message) {
                model.set('message', this.resultData.message);

                this.createView('messageField', 'views/fields/text', {
                    selector: '.field[data-name="message"]',
                    mode: 'detail',
                    inlineEditDisabled: true,
                    model: model,
                    name: 'message',
                });
            }
        },

        data: function () {
            return {
                resultData: this.options.resultData,
                defaultMessage: this.getLanguage().translate('optInIsConfirmed', 'messages', 'LeadCapture'),
            };
        },
    });
});
PK]#9RPcc1views/lead-capture/opt-in-confirmation-expired.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/lead-capture/opt-in-confirmation-expired', ['view', 'model'], function (Dep, Model) {

    return Dep.extend({

        template: 'lead-capture/opt-in-confirmation-expired',

        setup: function () {
            this.resultData = this.options.resultData;
        },

        data: function () {
            return {
                defaultMessage: this.getLanguage().translate('optInConfirmationExpired', 'messages', 'LeadCapture'),
            };
        },
    });
});
PK]2�^��	�	'views/lead-capture/fields/field-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/lead-capture/fields/field-list', ['views/fields/multi-enum'], function (Dep) {

    return Dep.extend({

        setupOptions: function () {
            this.params.options = [];
            this.translatedOptions = {};

            var fields = this.getMetadata()
                .get(['entityDefs', 'Lead', 'fields']) || {};

            var ignoreFieldList = this.getMetadata()
                .get(['entityDefs', 'LeadCapture', 'fields', 'fieldList', 'ignoreFieldList']) || [];

            for (let field in fields) {
                var defs = fields[field];

                if (defs.disabled) {
                    continue;
                }

                if (defs.readOnly) {
                    continue;
                }

                if (~ignoreFieldList.indexOf(field)) {
                    continue;
                }

                this.params.options.push(field);
                this.translatedOptions[field] = this.translate(field, 'fields', 'Lead');
            }
        }
    });
});
PK]�����$views/lead-capture/fields/api-key.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/lead-capture/fields/api-key', ['views/fields/varchar'], function (Dep) {

    return Dep.extend({
    });
});
PK]�&�s��)views/lead-capture/fields/smtp-account.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/lead-capture/fields/smtp-account', ['views/fields/enum'], function (Dep) {

    return Dep.extend({

        dataUrl: 'LeadCapture/action/smtpAccountDataList',

        getAttributeList: function () {
            return [this.name, 'inboundEmailId'];
        },

        data: function () {
            var data = Dep.prototype.data.call(this);

            data.valueIsSet = true;
            data.isNotEmpty = true;

            return data;
        },

        setupOptions: function () {
            Dep.prototype.setupOptions.call(this);

            this.params.options = [];
            this.translatedOptions = {};

            this.params.options.push('system');

            if (!this.loadedOptionList) {
                if (this.model.get('inboundEmailId')) {
                    var item = 'inboundEmail:' + this.model.get('inboundEmailId');

                    this.params.options.push(item);

                    this.translatedOptions[item] =
                        (this.model.get('inboundEmailName') || this.model.get('inboundEmailId')) +
                        ' (' + this.translate('group', 'labels', 'MassEmail') + ')';
                }
            } else {
                this.loadedOptionList.forEach((item) => {
                    this.params.options.push(item);

                    this.translatedOptions[item] =
                        (this.loadedOptionTranslations[item] || item) +
                        ' (' + this.translate('group', 'labels', 'MassEmail') + ')';
                });
            }

            this.translatedOptions['system'] =
                this.getConfig().get('outboundEmailFromAddress') +
                ' (' + this.translate('system', 'labels', 'MassEmail') + ')';
        },

        getValueForDisplay: function () {
            if (!this.model.has(this.name) && this.isReadMode()) {
                if (this.model.has('inboundEmailId')) {
                    if (this.model.get('inboundEmailId')) {
                        return 'inboundEmail:' + this.model.get('inboundEmailId');
                    } else {
                        return 'system';
                    }
                } else {
                    return '...';
                }
            }

            return this.model.get(this.name);
        },

        setup: function () {
            Dep.prototype.setup.call(this);

            if (
                this.getAcl().checkScope('MassEmail', 'create') ||
                this.getAcl().checkScope('MassEmail', 'edit')
            ) {

                Espo.Ajax.getRequest(this.dataUrl).then(dataList => {
                    if (!dataList.length) {
                        return;
                    }

                    this.loadedOptionList = [];

                    this.loadedOptionTranslations = {};
                    this.loadedOptionAddresses = {};
                    this.loadedOptionFromNames = {};

                    dataList.forEach(item => {
                        this.loadedOptionList.push(item.key);

                        this.loadedOptionTranslations[item.key] = item.emailAddress;
                        this.loadedOptionAddresses[item.key] = item.emailAddress;
                        this.loadedOptionFromNames[item.key] = item.fromName || '';
                    });

                    this.setupOptions();
                    this.reRender();
                });
            }
        },

        fetch: function () {
            var data = {};
            var value = this.$element.val();

            data[this.name] = value;

            if (!value || value === 'system') {
                data.inboundEmailId = null;
                data.inboundEmailName = null;
            }
            else {
                var arr = value.split(':');

                if (arr.length > 1) {
                    data.inboundEmailId = arr[1];
                    data.inboundEmailName = this.translatedOptions[data.inboundEmailId] || data.inboundEmailId;
                }
            }

            return data;
        },
    });
});
PK]�����+views/lead-capture/record/panels/request.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/lead-capture/record/panels/request', ['views/record/panels/side'], function (Dep) {

    return Dep.extend({

        fieldList: [
            'exampleRequestUrl',
            'exampleRequestMethod',
            'exampleRequestHeaders',
            'exampleRequestPayload'
        ],
    });
});
PK]P��bb#views/lead-capture/record/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/lead-capture/record/detail', ['views/record/detail'], function (Dep) {

    return Dep.extend({

        setupActionItems: function () {
            Dep.prototype.setupActionItems.call(this);

            this.dropdownItemList.push({
                'label': 'Generate New API Key',
                'name': 'generateNewApiKey',
            });
        },

        actionGenerateNewApiKey: function () {
            this.confirm(this.translate('confirmation', 'messages'), () => {
                Espo.Ajax.postRequest('LeadCapture/action/generateNewApiKey', {id: this.model.id})
                    .then(data => {
                        this.model.set(data);
                    });
            });
        },
    });
});
PK]�:�1%%!views/lead-capture/record/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/lead-capture/record/list', ['views/record/list'], function (Dep) {

    return Dep.extend({

        massActionList: ['remove', 'massUpdate', 'export'],

    });
});
PK]�͠�ee-views/admin/auth-token/record/detail-small.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/auth-token/record/detail-small', ['views/record/detail-small'], function (Dep) {

    return Dep.extend({

        sideDisabled: true,

        isWide: true,

        bottomView: 'views/record/detail-bottom',
    });
});
PK]���:
:
4views/admin/auth-token/record/row-actions/default.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/auth-token/record/row-actions/default', ['views/record/row-actions/default'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            this.listenTo(this.model, 'change:isActive', () => {
                setTimeout(() => {
                    this.reRender();
                }, 10);
            });
        },

        getActionList: function () {
            var list = [];

            list.push({
                action: 'quickView',
                label: 'View',
                data: {
                    id: this.model.id
                }
            });

            if (this.model.get('isActive')) {
                list.push({
                    action: 'setInactive',
                    label: 'Set Inactive',
                    data: {
                        id: this.model.id
                    }
                });
            }

            list.push({
                action: 'quickRemove',
                label: 'Remove',
                data: {
                    id: this.model.id
                }
            });

            return list;
        },
    });
});
PK]`�NX&&'views/admin/auth-token/record/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/auth-token/record/detail', ['views/record/detail'], function (Dep) {

    return Dep.extend({

        sideDisabled: true,

        readOnly: true,
    });
});
PK]~y:��
�
%views/admin/auth-token/record/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/auth-token/record/list', ['views/record/list'], function (Dep) {

    return Dep.extend({

        rowActionsView: 'views/admin/auth-token/record/row-actions/default',

        massActionList: ['remove', 'setInactive'],

        checkAllResultMassActionList: ['remove', 'setInactive'],

        massActionSetInactive: function () {
            let ids = null;
            let allResultIsChecked = this.allResultIsChecked;

            if (!allResultIsChecked) {
                ids = this.checkedList;
            }

            let attributes = {
                isActive: false,
            };

            Espo.Ajax
                .postRequest('MassAction', {
                    action: 'update',
                    entityType: this.entityType,
                    params: {
                        ids: ids || null,
                        where: (!ids || ids.length === 0) ? this.collection.getWhere() : null,
                        searchParams: (!ids || ids.length === 0) ? this.collection.data : null,
                    },
                    data: attributes,
                })
                .then(() => {
                    this.collection
                        .fetch()
                        .then(() => {
                            Espo.Ui.success(this.translate('Done'));

                            if (ids) {
                                ids.forEach(id => {
                                    this.checkRecord(id);
                                });
                            }
                        });
                });
        },

        actionSetInactive: function (data) {
            if (!data.id) {
                return;
            }

            var model = this.collection.get(data.id);

            if (!model) {
                return;
            }

            Espo.Ui.notify(this.translate('pleaseWait', 'messages'));

            model
                .save({'isActive': false}, {patch: true})
                .then(() => {
                    Espo.Ui.notify(false);
                });
        },
    });
});
PK]�ș**'views/admin/auth-token/modals/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/auth-token/modals/detail', ['views/modals/detail'], function (Dep) {

    return Dep.extend({

        sideDisabled: true,

        editDisabled: true,
    });
});
PK]���views/admin/auth-token/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/auth-token/list', ['views/list'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            this.menu.buttons = [];
        },

        getHeader: function () {
            return '<a href="#Admin">' + this.translate('Administration') + '</a>' +
                ' <span class="chevron-right"></span> ' +
                this.getLanguage().translate('Auth Tokens', 'labels', 'Admin');
        },

        updatePageTitle: function () {
            this.setPageTitle(this.getLanguage().translate('Auth Tokens', 'labels', 'Admin'));
        },
    });
});

PK]��Я#views/admin/panels/notifications.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/panels/notifications', ['view'], function (Dep) {

    return Dep.extend({

        template: 'admin/panels/notifications',

        data: function () {
            return {
                notificationList: this.notificationList,
            };
        },

        setup: function () {
            this.notificationList = [];

            Espo.Ajax.getRequest('Admin/action/adminNotificationList').then(notificationList => {
                this.notificationList = notificationList;

                if (this.isRendered() || this.isBeingRendered()) {
                    this.reRender();
                }
            });
        },
    });
});
PK]sK��u	u	views/admin/extensions/done.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/extensions/done', ['views/modal'], function (Dep) {

    return Dep.extend({

        cssName: 'done-modal',

        header: false,

        template: 'admin/extensions/done',

        createButton: true,

        data: function () {
            return {
                version: this.options.version,
                name: this.options.name,
                text: this.translate('extensionInstalled', 'messages', 'Admin')
                    .replace('{version}', this.options.version)
                    .replace('{name}', this.options.name)
            };
        },

        setup: function () {
            this.on('remove', () => {
                window.location.reload();
            });

            this.buttonList = [
                {
                    name: 'close',
                    label: 'Close',
                }
            ];

            this.header = this.getLanguage().translate('Installed successfully', 'labels', 'Admin');
        },
    });
});
PK]��X
X
views/admin/extensions/ready.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/extensions/ready', ['views/modal'], function (Dep) {

    return Dep.extend({

        cssName: 'ready-modal',

        header: false,

        template: 'admin/extensions/ready',

        createButton: true,

        data: function () {
            return {
                version: this.upgradeData.version,
                text: this.translate('installExtension', 'messages', 'Admin')
                    .replace('{version}', this.upgradeData.version)
                    .replace('{name}', this.upgradeData.name)
            };
        },

        setup: function () {
            this.buttonList = [
                {
                    name: 'run',
                    text: this.translate('Install', 'labels', 'Admin'),
                    style: 'danger',
                },
                {
                    name: 'cancel',
                    label: 'Cancel',
                },
            ];

            this.upgradeData = this.options.upgradeData;

            this.header = this.getLanguage().translate('Ready for installation', 'labels', 'Admin');
        },

        actionRun: function () {
            this.trigger('run');
            this.remove();
        },
    });
});
PK]0.��views/admin/extensions/index.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import Dep from 'view';
import SelectProvider from 'helpers/list/select-provider';

export default Dep.extend({

    template: 'admin/extensions/index',

    packageContents: null,

    events: {
        'change input[name="package"]': function (e) {
            this.$el.find('button[data-action="upload"]')
                .addClass('disabled')
                .attr('disabled', 'disabled');

            this.$el.find('.message-container').html('');

            let files = e.currentTarget.files;

            if (files.length) {
                this.selectFile(files[0]);
            }
        },
        'click button[data-action="upload"]': function () {
            this.upload();
        },
        'click [data-action="install"]': function (e) {
            let id = $(e.currentTarget).data('id');

            let name = this.collection.get(id).get('name');
            let version = this.collection.get(id).get('version');

            this.run(id, name, version);

        },
        'click [data-action="uninstall"]': function (e) {
            let id = $(e.currentTarget).data('id');

            this.confirm(this.translate('uninstallConfirmation', 'messages', 'Admin'), () => {
                Espo.Ui.notify(this.translate('Uninstalling...', 'labels', 'Admin'));

                Espo.Ajax
                    .postRequest('Extension/action/uninstall', {id: id}, {timeout: 0, bypassAppReload: true})
                    .then(() => {
                        window.location.reload();
                    })
                    .catch(xhr => {
                        let msg = xhr.getResponseHeader('X-Status-Reason');

                        this.showErrorNotification(this.translate('Error') + ': ' + msg);
                    });
            });
        }
    },

    setup: function () {
        const selectProvider = new SelectProvider(
            this.getHelper().layoutManager,
            this.getHelper().metadata,
            this.getHelper().fieldManager
        );

        this.wait(
            this.getCollectionFactory()
                .create('Extension')
                .then(collection => {
                    this.collection = collection;
                    this.collection.maxSize = this.getConfig().get('recordsPerPage');
                })
                .then(() => selectProvider.get('Extension'))
                .then(select => {
                    this.collection.data.select = select.join(',');
                })
                .then(() => this.collection.fetch())
                .then(() => {
                    this.createView('list', 'views/extension/record/list', {
                        collection: this.collection,
                        selector: '> .list-container',
                    });

                    if (this.collection.length === 0) {
                        this.once('after:render', () => {
                            this.$el.find('.list-container').addClass('hidden');
                        });
                    }
                })
        );
    },

    selectFile: function (file) {
        var fileReader = new FileReader();

        fileReader.onload = (e) => {
            this.packageContents = e.target.result;

            this.$el.find('button[data-action="upload"]')
                .removeClass('disabled')
                .removeAttr('disabled');
        };

        fileReader.readAsDataURL(file);
    },

    showError: function (msg) {
        msg = this.translate(msg, 'errors', 'Admin');

        this.$el.find('.message-container').html(msg);
    },

    showErrorNotification: function (msg) {
        if (!msg) {
            this.$el.find('.notify-text').addClass('hidden');

            return;
        }

        msg = this.translate(msg, 'errors', 'Admin');

        this.$el.find('.notify-text').html(msg);
        this.$el.find('.notify-text').removeClass('hidden');
    },

    upload: function () {
        this.$el.find('button[data-action="upload"]').addClass('disabled').attr('disabled', 'disabled');

        this.notify('Uploading...');

        Espo.Ajax
            .postRequest('Extension/action/upload', this.packageContents, {
                timeout: 0,
                contentType: 'application/zip',
            })
            .then(data => {
                if (!data.id) {
                    this.showError(this.translate('Error occurred'));

                    return;
                }

                Espo.Ui.notify(false);

                this.createView('popup', 'views/admin/extensions/ready', {
                    upgradeData: data,
                }, view => {
                    view.render();

                    this.$el.find('button[data-action="upload"]')
                        .removeClass('disabled')
                        .removeAttr('disabled');

                    view.once('run', () => {
                        view.close();

                        this.$el.find('.panel.upload').addClass('hidden');

                        this.run(data.id, data.version, data.name);
                    });
                });
            })
            .catch(xhr => {
                this.showError(xhr.getResponseHeader('X-Status-Reason'));

                Espo.Ui.notify(false);
            });
    },

    run: function (id, version, name) {
        Espo.Ui.notify(this.translate('pleaseWait', 'messages'));

        this.showError(false);
        this.showErrorNotification(false);

        Espo.Ajax
            .postRequest('Extension/action/install', {id: id}, {timeout: 0, bypassAppReload: true})
            .then(() => {
                let cache = this.getCache();

                if (cache) {
                    cache.clear();
                }

                this.createView('popup', 'views/admin/extensions/done', {
                    version: version,
                    name: name,
                }, view => {
                    if (this.collection.length) {
                        this.collection.fetch({bypassAppReload: true});
                    }

                    this.$el.find('.list-container').removeClass('hidden');
                    this.$el.find('.panel.upload').removeClass('hidden');

                    Espo.Ui.notify(false);

                    view.render();
                });
            })
            .catch(xhr => {
                this.$el.find('.panel.upload').removeClass('hidden');

                let msg = xhr.getResponseHeader('X-Status-Reason');

                this.showErrorNotification(this.translate('Error') + ': ' + msg);
            });
    },
});
PK]���Y��@views/admin/link-manager/fields/foreign-link-entity-type-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/link-manager/fields/foreign-link-entity-type-list', ['views/fields/checklist'], function (Dep) {

    return Dep.extend({

        setup: function () {
            this.params.translation = 'Global.scopeNames';

            Dep.prototype.setup.call(this);
        },

        afterRender: function () {
            Dep.prototype.afterRender.call(this);

            this.controlOptionsAvailability();
        },

        controlOptionsAvailability: function () {
            this.params.options.forEach(item => {
                var link = this.model.get('link');
                var linkForeign = this.model.get('linkForeign');
                var entityType = this.model.get('entity');

                var linkDefs = this.getMetadata().get(['entityDefs', item, 'links']) || {};

                var isFound = false;

                for (let i in linkDefs) {
                    if (
                        linkDefs[i].foreign === link &&
                        !linkDefs[i].isCustom &&
                        linkDefs[i].entity === entityType
                    ) {
                        isFound = true;
                    } else if (i === linkForeign && linkDefs[i].type !== 'hasChildren') {
                        isFound = true;
                    }
                }

                if (isFound) {
                    this.$el
                        .find('input[data-name="checklistItem-foreignLinkEntityTypeList-'+item+'"]')
                        .attr('disabled', 'disabled');
                }
            });
        },
    });
});
PK]g:4�8�8�'views/admin/link-manager/modals/edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ModalView from 'views/modal';
import Model from 'model';
import Index from 'views/admin/link-manager/index';
import EnumFieldView from 'views/fields/enum';

class LinkManagerEditModalView extends ModalView {

    template = 'admin/link-manager/modals/edit'
    cssName = 'edit'
    className = 'dialog dialog-record'

    shortcutKeys = {
        /** @this LinkManagerEditModalView */
        'Control+KeyS': function (e) {
            this.save({noClose: true});

            e.preventDefault();
            e.stopPropagation();
        },
        /** @this LinkManagerEditModalView */
        'Control+Enter': function (e) {
            this.save();

            e.preventDefault();
            e.stopPropagation();
        },
    }

    setup() {
        this.buttonList = [
            {
                name: 'save',
                label: 'Save',
                style: 'danger',
                onClick: () => {
                    this.save();
                },
            },
            {
                name: 'cancel',
                label: 'Cancel',
                onClick: () => {
                    this.close();
                },
            }
        ];

        let scope = this.scope = this.options.scope;
        let link = this.link = this.options.link || false;

        let entity = scope;

        let isNew = this.isNew = (false === link);

        let header = 'Create Link';

        if (!isNew) {
            header = 'Edit Link';
        }

        this.headerText = this.translate(header, 'labels', 'Admin');

        let model = this.model = new Model();
        model.name = 'EntityManager';

        this.model.set('entity', scope);

        let allEntityList = this.getMetadata().getScopeEntityList()
            .filter(item => {
                return this.getMetadata().get(['scopes', item, 'customizable']);
            })
            .sort((v1, v2) => {
                var t1 = this.translate(v1, 'scopeNames');
                var t2 = this.translate(v2, 'scopeNames');

                return t1.localeCompare(t2);
            });

        let isCustom = true;

        let linkType;

        if (!isNew) {
            let entityForeign = this.getMetadata().get('entityDefs.' + scope + '.links.' + link + '.entity');
            let linkForeign = this.getMetadata().get('entityDefs.' + scope + '.links.' + link + '.foreign');
            let label = this.getLanguage().translate(link, 'links', scope);
            let labelForeign = this.getLanguage().translate(linkForeign, 'links', entityForeign);

            let type = this.getMetadata().get('entityDefs.' + entity + '.links.' + link + '.type');
            let foreignType = this.getMetadata()
                .get('entityDefs.' + entityForeign + '.links.' + linkForeign + '.type');

            if (type === 'belongsToParent') {
                linkType = 'childrenToParent';

                labelForeign = null;

                let entityTypeList = this.getMetadata()
                    .get(['entityDefs', entity, 'fields', link, 'entityList']) || [];

                if (this.getMetadata().get(['entityDefs', entity, 'fields', link, 'entityList']) === null) {
                    entityTypeList = allEntityList;

                    this.noParentEntityTypeList = true;
                }

                this.model.set('parentEntityTypeList', entityTypeList);

                let foreignLinkEntityTypeList = this.getForeignLinkEntityTypeList(entity, link, entityTypeList);

                this.model.set('foreignLinkEntityTypeList', foreignLinkEntityTypeList);
            }
            else {
                linkType = Index.prototype.computeRelationshipType.call(this, type, foreignType);
            }

            this.model.set('linkType', linkType);
            this.model.set('entityForeign', entityForeign);
            this.model.set('link', link);
            this.model.set('linkForeign', linkForeign);
            this.model.set('label', label);
            this.model.set('labelForeign', labelForeign);

            let linkMultipleField =
                this.getMetadata().get(['entityDefs', scope, 'fields', link, 'type']) === 'linkMultiple' &&
                !this.getMetadata().get(['entityDefs', scope, 'fields', link, 'noLoad']);

            let linkMultipleFieldForeign =
                this.getMetadata()
                    .get(['entityDefs', entityForeign, 'fields', linkForeign, 'type']) === 'linkMultiple' &&
                !this.getMetadata().get(['entityDefs', entityForeign, 'fields', linkForeign, 'noLoad']);

            this.model.set('linkMultipleField', linkMultipleField);
            this.model.set('linkMultipleFieldForeign', linkMultipleFieldForeign);

            if (linkType === 'manyToMany') {
                let relationName = this.getMetadata()
                    .get('entityDefs.' + entity + '.links.' + link + '.relationName');

                this.model.set('relationName', relationName);
            }

            let audited = this.getMetadata().get(['entityDefs', scope, 'links', link, 'audited']) || false;
            let auditedForeign = this.getMetadata()
                .get(['entityDefs', entityForeign, 'links', linkForeign, 'audited']) || false;

            this.model.set('audited', audited);
            this.model.set('auditedForeign', auditedForeign);

            let layout = this.getMetadata()
                .get(['clientDefs', scope, 'relationshipPanels', link, 'layout']);
            let layoutForeign = this.getMetadata()
                .get(['clientDefs', entityForeign, 'relationshipPanels', linkForeign, 'layout']);

            this.model.set('layout', layout);
            this.model.set('layoutForeign', layoutForeign);

            isCustom = this.getMetadata().get('entityDefs.' + entity + '.links.' + link + '.isCustom');
        }

        let scopes = this.getMetadata().get('scopes') || null;

        let entityList = (Object.keys(scopes) || [])
            .filter(item => {
                let d = scopes[item];

                return d.customizable && d.entity;
            })
            .sort((v1, v2) => {
                let t1 = this.translate(v1, 'scopeNames');
                let t2 = this.translate(v2, 'scopeNames');

                return t1.localeCompare(t2);
            });

        entityList.unshift('');

        this.createView('entity', 'views/fields/varchar', {
            model: model,
            mode: 'edit',
            selector: '.field[data-name="entity"]',
            defs: {
                name: 'entity'
            },
            readOnly: true,
        });

        this.createView('entityForeign', 'views/fields/enum', {
            model: model,
            mode: 'edit',
            selector: '.field[data-name="entityForeign"]',
            defs: {
                name: 'entityForeign',
                params: {
                    required: true,
                    options: entityList,
                    translation: 'Global.scopeNames',
                }
            },
            readOnly: !isNew,
        });

        this.createView('linkType', 'views/fields/enum', {
            model: model,
            mode: 'edit',
            selector: '.field[data-name="linkType"]',
            defs: {
                name: 'linkType',
                params: {
                    required: true,
                    options: ['', 'oneToMany', 'manyToOne', 'manyToMany',
                        'oneToOneRight', 'oneToOneLeft', 'childrenToParent']
                }
            },
            readOnly: !isNew,
        });

        this.createView('link', 'views/fields/varchar', {
            model: model,
            mode: 'edit',
            selector: '.field[data-name="link"]',
            defs: {
                name: 'link',
                params: {
                    required: true,
                    trim: true,
                    maxLength: 61,
                },
            },
            readOnly: !isNew,
        });

        this.createView('linkForeign', 'views/fields/varchar', {
            model: model,
            mode: 'edit',
            selector: '.field[data-name="linkForeign"]',
            defs: {
                name: 'linkForeign',
                params: {
                    required: true,
                    trim: true,
                    maxLength: 61,
                },
            },
            readOnly: !isNew,
        });

        this.createView('label', 'views/fields/varchar', {
            model: model,
            mode: 'edit',
            selector: '.field[data-name="label"]',
            defs: {
                name: 'label',
                params: {
                    required: true,
                    trim: true,
                },
            },
        });

        this.createView('labelForeign', 'views/fields/varchar', {
            model: model,
            mode: 'edit',
            selector: '.field[data-name="labelForeign"]',
            defs: {
                name: 'labelForeign',
                params: {
                    required: true,
                    trim: true,
                },
            },
        });

        if (isNew || this.model.get('relationName')) {
            this.createView('relationName', 'views/fields/varchar', {
                model: model,
                mode: 'edit',
                selector: '.field[data-name="relationName"]',
                defs: {
                    name: 'relationName',
                    params: {
                        required: true,
                        trim: true,
                        maxLength: 61,
                    },
                },
                readOnly: !isNew,
            });
        }

        this.createView('linkMultipleField', 'views/fields/bool', {
            model: model,
            mode: 'edit',
            selector: '.field[data-name="linkMultipleField"]',
            defs: {
                name: 'linkMultipleField'
            },
            readOnly: !isCustom,
            tooltip: true,
            tooltipText: this.translate('linkMultipleField', 'tooltips', 'EntityManager'),
        });

        this.createView('linkMultipleFieldForeign', 'views/fields/bool', {
            model: model,
            mode: 'edit',
            selector: '.field[data-name="linkMultipleFieldForeign"]',
            defs: {
                name: 'linkMultipleFieldForeign'
            },
            readOnly: !isCustom,
            tooltip: true,
            tooltipText: this.translate('linkMultipleField', 'tooltips', 'EntityManager'),
        });

        this.createView('audited', 'views/fields/bool', {
            model: model,
            mode: 'edit',
            selector: '.field[data-name="audited"]',
            defs: {
                name: 'audited'
            },
            tooltip: true,
            tooltipText: this.translate('linkAudited', 'tooltips', 'EntityManager'),
        });

        this.createView('auditedForeign', 'views/fields/bool', {
            model: model,
            mode: 'edit',
            selector: '.field[data-name="auditedForeign"]',
            defs: {
                name: 'auditedForeign'
            },
            tooltip: true,
            tooltipText: this.translate('linkAudited', 'tooltips', 'EntityManager'),
        });

        let layouts = ['', ...this.getEntityTypeLayouts(this.scope)];
        let layoutTranslatedOptions = this.getEntityTypeLayoutsTranslations(this.scope);

        this.layoutFieldView = new EnumFieldView({
            model: model,
            mode: 'edit',
            defs: {
                name: 'layout',
            },
            params: {
                options: [''],
            },
        });

        this.layoutForeignFieldView = new EnumFieldView({
            model: model,
            mode: 'edit',
            defs: {
                name: 'layoutForeign',
            },

            params: {
                options: layouts,
            },
            translatedOptions: layoutTranslatedOptions,
        });

        this.assignView('layout', this.layoutFieldView, '.field[data-name="layout"]');
        this.assignView('layoutForeign', this.layoutForeignFieldView, '.field[data-name="layoutForeign"]');

        this.createView('parentEntityTypeList', 'views/fields/entity-type-list', {
            model: model,
            mode: 'edit',
            selector: '.field[data-name="parentEntityTypeList"]',
            defs: {
                name: 'parentEntityTypeList',
            },
        });

        this.createView('foreignLinkEntityTypeList',
                'views/admin/link-manager/fields/foreign-link-entity-type-list',
            {
                model: model,
                mode: 'edit',
                selector: '.field[data-name="foreignLinkEntityTypeList"]',
                defs: {
                    name: 'foreignLinkEntityTypeList',
                    params: {
                        options: this.model.get('parentEntityTypeList') || [],
                    },
                },
            });

        this.model.fetchedAttributes = this.model.getClonedAttributes();

        this.listenTo(this.model, 'change', () => {
            if (
                !this.model.hasChanged('parentEntityTypeList') &&
                !this.model.hasChanged('linkForeign') &&
                !this.model.hasChanged('link')
            ) {
                return;
            }

            let view = this.getView('foreignLinkEntityTypeList');

            if (view) {
                if (!this.noParentEntityTypeList) {
                    view.setOptionList(this.model.get('parentEntityTypeList') || []);
                }
            }

            let checkedList = Espo.Utils.clone(this.model.get('foreignLinkEntityTypeList') || []);

            this.getForeignLinkEntityTypeList(
                this.model.get('entity'),
                this.model.get('link'), this.model.get('parentEntityTypeList') || [], true
            )
                .forEach(item => {
                    if (!~checkedList.indexOf(item)) {
                        checkedList.push(item);
                    }
                });

            this.model.set('foreignLinkEntityTypeList', checkedList);
        });

        this.controlLayoutField();
        this.listenTo(this.model, 'change:entityForeign', () => this.controlLayoutField());
    }

    getEntityTypeLayouts(entityType) {
        let defs = this.getMetadata().get(['clientDefs', entityType, 'additionalLayouts'], {});

        return Object.keys(defs)
            .filter(item => ['list', 'listSmall'].includes(defs[item].type));
    }

    getEntityTypeLayoutsTranslations(entityType) {
        let map = {};

        this.getEntityTypeLayouts(entityType).forEach(item => {
            map[item] = this.getLanguage().has(item, 'layouts', entityType) ?
                this.getLanguage().translate(item, 'layouts', entityType) :
                this.getLanguage().translate(item, 'layouts', 'Admin');
        });

        return map;
    }

    controlLayoutField() {
        let foreignEntityType = this.model.get('entityForeign');

        let layouts = foreignEntityType ?
            ['', ...this.getEntityTypeLayouts(foreignEntityType)] :
            [''];

        this.layoutFieldView.translatedOptions = foreignEntityType ?
            this.getEntityTypeLayoutsTranslations(foreignEntityType) :
            {};

        this.layoutFieldView.setOptionList(layouts);
    }

    toPlural(string) {
        if (string.slice(-1) === 'y') {
            return string.substr(0, string.length - 1) + 'ies';
        }

        if (string.slice(-1) === 's') {
            return string.substr(0, string.length) + 'es';
        }

        return string + 's';
    }

    populateFields() {
        let entityForeign = this.model.get('entityForeign');
        let linkType = this.model.get('linkType');

        let link;
        let linkForeign;

        if (linkType === 'childrenToParent') {
                this.model.set('link', 'parent');
                this.model.set('label', 'Parent');

                linkForeign = this.toPlural(Espo.Utils.lowerCaseFirst(this.scope));

                if (this.getMetadata().get(['entityDefs', this.scope, 'links', 'parent'])) {
                    this.model.set('link', 'parentAnother');
                    this.model.set('label', 'Parent Another');

                    linkForeign += 'Another';
                }

                this.model.set('linkForeign', linkForeign);

                this.model.set('labelForeign', '');
                this.model.set('entityForeign', null);

                return;
        }
        else {
            if (!entityForeign || !linkType) {
                this.model.set('link', '');
                this.model.set('linkForeign', '');

                this.model.set('label', '');
                this.model.set('labelForeign', '');

                return;
            }
        }

        switch (linkType) {
            case 'oneToMany':
                linkForeign = Espo.Utils.lowerCaseFirst(this.scope);
                link = this.toPlural(Espo.Utils.lowerCaseFirst(entityForeign));

                if (entityForeign === this.scope) {

                    if (linkForeign === Espo.Utils.lowerCaseFirst(this.scope)) {
                        linkForeign = linkForeign + 'Parent';
                    }
                }

                break;

            case 'manyToOne':
                linkForeign = this.toPlural(Espo.Utils.lowerCaseFirst(this.scope));
                link = Espo.Utils.lowerCaseFirst(entityForeign);

                if (entityForeign === this.scope) {
                    if (link === Espo.Utils.lowerCaseFirst(this.scope)) {
                        link = link + 'Parent';
                    }
                }
                break;

            case 'manyToMany':
                linkForeign = this.toPlural(Espo.Utils.lowerCaseFirst(this.scope));
                link = this.toPlural(Espo.Utils.lowerCaseFirst(entityForeign));

                if (link === linkForeign) {
                    link = link + 'Right';
                    linkForeign = linkForeign + 'Left';
                }

                let relationName;

                if (this.scope.localeCompare(entityForeign)) {
                    relationName = Espo.Utils.lowerCaseFirst(this.scope) + entityForeign;
                } else {
                    relationName = Espo.Utils.lowerCaseFirst(entityForeign) + this.scope;
                }

                this.model.set('relationName', relationName);

                break;

            case 'oneToOneLeft':
                linkForeign = Espo.Utils.lowerCaseFirst(this.scope);
                link = Espo.Utils.lowerCaseFirst(entityForeign);

                if (entityForeign === this.scope) {
                    if (linkForeign === Espo.Utils.lowerCaseFirst(this.scope)) {
                        link = link + 'Parent';
                    }
                }

                break;

            case 'oneToOneRight':
                linkForeign = Espo.Utils.lowerCaseFirst(this.scope);
                link = Espo.Utils.lowerCaseFirst(entityForeign);

                if (entityForeign === this.scope) {
                    if (linkForeign === Espo.Utils.lowerCaseFirst(this.scope)) {
                        linkForeign = linkForeign + 'Parent';
                    }
                }

                break;
        }

        let number = 1;

        while (this.getMetadata().get(['entityDefs', this.scope, 'links', link])) {
            link += number.toString();

            number++;
        }

        number = 1;

        while (this.getMetadata().get(['entityDefs', entityForeign, 'links', linkForeign])) {
            linkForeign += number.toString();

            number++;
        }

        this.model.set('link', link);
        this.model.set('linkForeign', linkForeign);

        let label = Espo.Utils.upperCaseFirst(link.replace(/([a-z])([A-Z])/g, '$1 $2'));
        let labelForeign = Espo.Utils.upperCaseFirst(linkForeign.replace(/([a-z])([A-Z])/g, '$1 $2'));

        this.model.set('label', label);
        this.model.set('labelForeign', labelForeign);
    }

    handleLinkChange(field) {
        let value = this.model.get(field);

        if (value) {
            value = value.replace(/-/g, ' ')
                .replace(/_/g, ' ')
                .replace(/[^\w\s]/gi, '').replace(/ (.)/g, (match, g) => {
                    return g.toUpperCase();
                })
                .replace(' ', '');

            if (value.length) {
                 value = Espo.Utils.lowerCaseFirst(value);
            }
        }

        this.model.set(field, value);
    }

    hideField(name) {
        let view = this.getView(name);

        if (view) {
            view.disabled = true;
        }

        this.$el.find('.cell[data-name=' + name+']').addClass('hidden-cell');
    }

    showField(name) {
        let view = this.getView(name);

        if (view) {
            view.disabled = false;
        }

        this.$el.find('.cell[data-name=' + name+']').removeClass('hidden-cell');
    }

    handleLinkTypeChange() {
        var linkType = this.model.get('linkType');

        this.showField('entityForeign');
        this.showField('labelForeign');

        this.hideField('parentEntityTypeList');
        this.hideField('foreignLinkEntityTypeList');

        if (linkType === 'manyToMany') {
            this.showField('relationName');

            this.showField('linkMultipleField');
            this.showField('linkMultipleFieldForeign');

            this.showField('audited');
            this.showField('auditedForeign');

            this.showField('layout');
            this.showField('layoutForeign');
        }
        else {
            this.hideField('relationName');

            if (linkType === 'oneToMany') {
                this.showField('linkMultipleField');
                this.hideField('linkMultipleFieldForeign');

                this.showField('audited');
                this.hideField('auditedForeign');

                this.showField('layout');
                this.hideField('layoutForeign');
            }
            else if (linkType === 'manyToOne') {
                this.hideField('linkMultipleField');
                this.showField('linkMultipleFieldForeign');

                this.hideField('audited');
                this.showField('auditedForeign');

                this.hideField('layout');
                this.showField('layoutForeign');
            }
            else {
                this.hideField('linkMultipleField');
                this.hideField('linkMultipleFieldForeign');

                this.hideField('audited');
                this.hideField('auditedForeign');

                this.hideField('layout');
                this.hideField('layoutForeign');

                if (linkType === 'parentToChildren') {
                    this.showField('audited');
                    this.hideField('auditedForeign');

                    this.showField('layout');
                    this.hideField('layoutForeign');
                }
                else if (linkType === 'childrenToParent') {
                    this.hideField('audited');
                    this.showField('auditedForeign');

                    this.hideField('layout');
                    this.hideField('layoutForeign');

                    this.hideField('entityForeign');
                    this.hideField('labelForeign');

                    if (!this.noParentEntityTypeList) {
                        this.showField('parentEntityTypeList');
                    }

                    if (!this.model.get('linkForeign')) {
                        this.hideField('foreignLinkEntityTypeList');
                    } else {
                        this.showField('foreignLinkEntityTypeList');
                    }
                }
                else {
                    this.hideField('audited');
                    this.hideField('auditedForeign');

                    this.hideField('layout');
                    this.hideField('layoutForeign');
                }
            }
        }

        if (!this.getMetadata().get(['scopes', this.scope, 'stream'])) {
            this.hideField('audited');
        }

        if (!this.getMetadata().get(['scopes', this.model.get('entityForeign'), 'stream'])) {
            this.hideField('auditedForeign');
        }
    }

    afterRender() {
        this.handleLinkTypeChange();

        this.getView('linkType').on('change', () => {
            this.handleLinkTypeChange();
            this.populateFields();
        });

        this.getView('entityForeign').on('change', () => {
            this.populateFields();
        });

        this.getView('link').on('change', () => {
            this.handleLinkChange('link');
        });

        this.getView('linkForeign').on('change', () => {
            this.handleLinkChange('linkForeign');
        });
    }

    /**
     * @param {{noClose?: boolean}} [options]
     */
    save(options) {
        options = options || {};

        let arr = [
            'link',
            'linkForeign',
            'label',
            'labelForeign',
            'linkType',
            'entityForeign',
            'relationName',
            'linkMultipleField',
            'linkMultipleFieldForeign',
            'audited',
            'auditedForeign',
            'layout',
            'layoutForeign',
            'parentEntityTypeList',
            'foreignLinkEntityTypeList',
        ];

        let notValid = false;

        arr.forEach(item => {
            if (!this.hasView(item)) {
                return;
            }

            if (this.getView(item).mode !== 'edit') {
                return;
            }

            this.getView(item).fetchToModel();
        });

        arr.forEach(item => {
            if (!this.hasView(item)) {
                return;
            }

            let view = this.getView(item);

            if (view.mode !== 'edit') {
                return;
            }

            if (!view.disabled) {
                notValid = view.validate() || notValid;
            }
        });

        if (notValid) {
            return;
        }

        this.$el.find('button[data-name="save"]').addClass('disabled').attr('disabled');

        let url = 'EntityManager/action/createLink';

        if (!this.isNew) {
            url = 'EntityManager/action/updateLink';
        }

        let entity = this.scope;
        let entityForeign = this.model.get('entityForeign');
        let link = this.model.get('link');
        let linkForeign = this.model.get('linkForeign');
        let label = this.model.get('label');
        let labelForeign = this.model.get('labelForeign');
        let relationName = this.model.get('relationName');

        let linkMultipleField = this.model.get('linkMultipleField');
        let linkMultipleFieldForeign = this.model.get('linkMultipleFieldForeign');

        let audited = this.model.get('audited');
        let auditedForeign = this.model.get('auditedForeign');

        let layout = this.model.get('layout');
        let layoutForeign = this.model.get('layoutForeign');

        let linkType = this.model.get('linkType');

        let attributes = {
            entity: entity,
            entityForeign: entityForeign,
            link: link,
            linkForeign: linkForeign,
            label: label,
            labelForeign: labelForeign,
            linkType: linkType,
            relationName: relationName,
            linkMultipleField: linkMultipleField,
            linkMultipleFieldForeign: linkMultipleFieldForeign,
            audited: audited,
            auditedForeign: auditedForeign,
            layout: layout,
            layoutForeign: layoutForeign,
        };

        if (!this.isNew) {
            if (attributes.label === this.model.fetchedAttributes.label) {
                delete attributes.label;
            }

            if (attributes.labelForeign === this.model.fetchedAttributes.labelForeign) {
                delete attributes.labelForeign;
            }
        }

        if (linkType === 'childrenToParent') {
            delete attributes.entityForeign;
            delete attributes.labelForeign;

            attributes.parentEntityTypeList = this.model.get('parentEntityTypeList');
            attributes.foreignLinkEntityTypeList = this.model.get('foreignLinkEntityTypeList');

            if (this.noParentEntityTypeList) {
                attributes.parentEntityTypeList = null;
            }
        }

        Espo.Ajax
            .postRequest(url, attributes)
            .then(() => {
                if (!this.isNew) {
                    Espo.Ui.success(this.translate('Saved'));
                }
                else {
                    Espo.Ui.success(this.translate('Created'));
                }

                this.model.fetchedAttributes = this.model.getClonedAttributes();

                Promise.all([
                    this.getMetadata().loadSkipCache(),
                    this.getLanguage().loadSkipCache(),
                ]).then(() => {
                    this.broadcastUpdate();
                    this.trigger('after:save');

                    if (!options.noClose) {
                        this.close();
                    }

                    if (options.noClose) {
                        this.$el.find('button[data-name="save"]')
                            .removeClass('disabled')
                            .removeAttr('disabled');
                    }
                });
            })
            .catch(xhr => {
                if (xhr.status === 409) {
                    var msg = this.translate('linkConflict', 'messages', 'EntityManager');
                    var statusReasonHeader = xhr.getResponseHeader('X-Status-Reason');

                    if (statusReasonHeader) {
                        console.error(statusReasonHeader);
                    }

                    Espo.Ui.error(msg);

                    xhr.errorIsHandled = true;
                }

                this.$el.find('button[data-name="save"]').removeClass('disabled').removeAttr('disabled');
            });
    }

    getForeignLinkEntityTypeList(entityType, link, entityTypeList, onlyNotCustom) {
        let list = [];

        entityTypeList.forEach(item => {
            let linkDefs = this.getMetadata().get(['entityDefs', item, 'links']) || {};

            let isFound = false;

            for (let i in linkDefs) {
                if (
                    linkDefs[i].foreign === link &&
                    linkDefs[i].entity === entityType &&
                    linkDefs[i].type === 'hasChildren'
                ) {
                    if (onlyNotCustom) {
                        if (linkDefs[i].isCustom) {
                            continue;
                        }
                    }

                    isFound = true;

                    break;
                }
            }

            if (isFound) {
                list.push(item);
            }
        });

        return list;
    }

    broadcastUpdate() {
        this.getHelper().broadcastChannel.postMessage('update:metadata');
        this.getHelper().broadcastChannel.postMessage('update:language');
    }
}

export default LinkManagerEditModalView;
PK]_G�*)*)!views/admin/link-manager/index.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/admin/link-manager/index */

import View from 'view';

class LinkManagerIndexView extends View {

    template = 'admin/link-manager/index'

    scope = null

    data() {
        return {
            linkDataList: this.linkDataList,
            scope: this.scope,
            isCreatable: this.isCustomizable,
        };
    }

    events = {
        /** @this LinkManagerIndexView */
        'click a[data-action="editLink"]': function (e) {
            var link = $(e.currentTarget).data('link');

            this.editLink(link);
        },
        /** @this LinkManagerIndexView */
        'click button[data-action="createLink"]': function () {
            this.createLink();
        },
        /** @this LinkManagerIndexView */
        'click [data-action="removeLink"]': function (e) {
            var link = $(e.currentTarget).data('link');
            this.confirm(this.translate('confirmation', 'messages'), function () {
                this.removeLink(link);
            }, this);
        },
        /** @this LinkManagerIndexView */
        'keyup input[data-name="quick-search"]': function (e) {
            this.processQuickSearch(e.currentTarget.value);
        },
    }

    computeRelationshipType(type, foreignType) {
        if (type === 'hasMany') {
            if (foreignType === 'hasMany') {
                return 'manyToMany';
            }
            else if (foreignType === 'belongsTo') {
                return 'oneToMany';
            }
            else {
                return undefined;
            }
        }
        else if (type === 'belongsTo') {
            if (foreignType === 'hasMany') {
                return 'manyToOne';
            }
            else if (foreignType === 'hasOne') {
                return 'oneToOneRight';
            }
            else {
                return undefined;
            }
        }
        else if (type === 'belongsToParent') {
            if (foreignType === 'hasChildren') {
                return 'childrenToParent'
            }

            return undefined;
        }
        else if (type === 'hasChildren') {
            if (foreignType === 'belongsToParent') {
                return 'parentToChildren'
            }

            return undefined;
        }
        else if (type === 'hasOne') {
            if (foreignType === 'belongsTo') {
                return 'oneToOneLeft';
            }

            return undefined;
        }
    }

    setupLinkData() {
        this.linkDataList = [];

        this.isCustomizable =
            !!this.getMetadata().get(`scopes.${this.scope}.customizable`) &&
            this.getMetadata().get(`scopes.${this.scope}.entityManager.relationships`) !== false;

        const links = this.getMetadata().get('entityDefs.' + this.scope + '.links');

        const linkList = Object.keys(links).sort((v1, v2) => {
            return v1.localeCompare(v2);
        });

        linkList.forEach(link => {
            var d = links[link];
            let type;

            var linkForeign = d.foreign;

            if (d.type === 'belongsToParent') {
                type = 'childrenToParent';
            }
            else {
                if (!d.entity) {
                    return;
                }

                if (!linkForeign) {
                    return;
                }

                var foreignType = this.getMetadata()
                    .get('entityDefs.' + d.entity + '.links.' + d.foreign + '.type');

                type = this.computeRelationshipType(d.type, foreignType);
            }

            if (!type) {
                return;
            }

            this.linkDataList.push({
                link: link,
                isCustom: d.isCustom,
                isRemovable: d.isCustom,
                customizable: d.customizable,
                isEditable: this.isCustomizable,
                type: type,
                entityForeign: d.entity,
                entity: this.scope,
                labelEntityForeign: this.getLanguage().translate(d.entity, 'scopeNames'),
                linkForeign: linkForeign,
                label: this.getLanguage().translate(link, 'links', this.scope),
                labelForeign: this.getLanguage().translate(d.foreign, 'links', d.entity),
            });
        });
    }

    setup() {
        this.scope = this.options.scope || null;

        this.setupLinkData();

        this.on('after:render', () => {
            this.renderHeader();
        });
    }

    afterRender() {
        this.$noData = this.$el.find('.no-data');

        this.$el.find('input[data-name="quick-search"]').focus();
    }

    createLink() {
        this.createView('edit', 'views/admin/link-manager/modals/edit', {
            scope: this.scope,
        }, view => {
            view.render();

            this.listenTo(view, 'after:save', () => {
                this.clearView('edit');

                this.setupLinkData();
                this.render();
            });

            this.listenTo(view, 'close', () => {
                this.clearView('edit');
            });
        });
    }

    editLink(link) {
        this.createView('edit', 'views/admin/link-manager/modals/edit', {
            scope: this.scope,
            link: link,
        }, view => {
            view.render();

            this.listenTo(view, 'after:save', () => {
                this.clearView('edit');

                this.setupLinkData();
                this.render();
            });

            this.listenTo(view, 'close', () => {
                this.clearView('edit');
            });
        });
    }

    removeLink(link) {
        Espo.Ajax
            .postRequest('EntityManager/action/removeLink', {
                entity: this.scope,
                link: link,
            })
            .then(() => {
                this.$el.find('table tr[data-link="'+link+'"]').remove();

                this.getMetadata().loadSkipCache().then(() => {
                    this.setupLinkData();

                    Espo.Ui.success(this.translate('Removed'), {suppress: true});

                    this.reRender();
                });
            });
    }

    renderHeader() {
        if (!this.scope) {
            $('#scope-header').html('');

            return;
        }

        $('#scope-header').show().html(this.getLanguage().translate(this.scope, 'scopeNames'));
    }

    updatePageTitle() {
        this.setPageTitle(this.getLanguage().translate('Entity Manager', 'labels', 'Admin'));
    }

    processQuickSearch(text) {
        text = text.trim();

        let $noData = this.$noData;

        $noData.addClass('hidden');

        if (!text) {
            this.$el.find('table tr.link-row').removeClass('hidden');

            return;
        }

        let matchedList = [];

        let lowerCaseText = text.toLowerCase();

        this.linkDataList.forEach(item => {
            let matched = false;

            let label = item.label || '';
            let link = item.link || '';
            let entityForeign = item.entityForeign || '';
            let labelEntityForeign = item.labelEntityForeign || '';

            if (
                label.toLowerCase().indexOf(lowerCaseText) === 0 ||
                link.toLowerCase().indexOf(lowerCaseText) === 0 ||
                entityForeign.toLowerCase().indexOf(lowerCaseText) === 0 ||
                labelEntityForeign.toLowerCase().indexOf(lowerCaseText) === 0
            ) {
                matched = true;
            }

            if (!matched) {
                let wordList = link.split(' ')
                    .concat(
                        label.split(' ')
                    )
                    .concat(
                        entityForeign.split(' ')
                    )
                    .concat(
                        labelEntityForeign.split(' ')
                    );

                wordList.forEach((word) => {
                    if (word.toLowerCase().indexOf(lowerCaseText) === 0) {
                        matched = true;
                    }
                });
            }

            if (matched) {
                matchedList.push(link);
            }
        });

        if (matchedList.length === 0) {
            this.$el.find('table tr.link-row').addClass('hidden');

            $noData.removeClass('hidden');

            return;
        }

        this.linkDataList
            .map(item => item.link)
            .forEach(scope => {
                if (!~matchedList.indexOf(scope)) {
                    this.$el.find('table tr.link-row[data-link="'+scope+'"]').addClass('hidden');

                    return;
                }

                this.$el.find('table tr.link-row[data-link="'+scope+'"]').removeClass('hidden');
            });
    }
}

export default LinkManagerIndexView;
PK]���}}views/admin/job/fields/name.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/job/fields/name', ['views/fields/varchar'], function (Dep) {

    return Dep.extend({

        getValueForDisplay: function () {
            if (this.mode === 'list' || this.mode === 'detail' || this.mode === 'listLink') {
                if (!this.model.get('name')) {
                    return this.model.get('serviceName') + ': ' + this.model.get('methodName');
                } else {
                    return this.model.get('name');
                }
            }
        },
    });
});
PK]�9##&views/admin/job/record/detail-small.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/job/record/detail-small', ['views/record/detail-small'], function (Dep) {

    return Dep.extend({

        sideView: null,
        isWide: true,
    });
});
PK]1�&ssviews/admin/job/record/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/job/record/list', ['views/record/list'], function (Dep) {

    return Dep.extend({

        rowActionsView: 'views/record/row-actions/view-and-remove',
        massActionList: ['remove'],
        rowActionsColumnWidth: '5%',
    });
});
PK]b�6x%% views/admin/job/modals/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/job/modals/detail', ['views/modals/detail'], function (Dep) {

    return Dep.extend({

        editDisabled: true,
        fullFormDisabled: true,
    });
});
PK]c��v	v	views/admin/job/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/job/list', ['views/list'], function (Dep) {

    return Dep.extend({

        createButton: false,

        setup: function () {
            Dep.prototype.setup.call(this);

            if (!this.getHelper().getAppParam('isRestrictedMode') || this.getUser().isSuperAdmin()) {
                this.addMenuItem('buttons', {
                    link: '#Admin/jobsSettings',
                    text: this.translate('Settings', 'labels', 'Admin'),
                });
            }
        },

        getHeader: function () {
            return this.buildHeaderHtml([
                $('<a>')
                    .attr('href', '#Admin')
                    .text(this.translate('Administration')),
                $('<span>')
                    .text(this.getLanguage().translate('Jobs', 'labels', 'Admin')),
            ]);
        },

        updatePageTitle: function () {
            this.setPageTitle(this.getLanguage().translate('Jobs', 'labels', 'Admin'));
        },
    });
});
PK]K�=��4views/admin/authentication/fields/test-connection.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/authentication/fields/test-connection', ['views/fields/base'], function (Dep) {

    return Dep.extend({

        templateContent: `
            <button
                class="btn btn-default"
                data-action="testConnection"
            >{{translate \'Test Connection\' scope=\'Settings\'}}</button>
        `,

        events: {
            'click [data-action="testConnection"]': function () {
                this.testConnection();
            },
        },

        fetch: function () {
            return {};
        },

        getConnectionData: function () {
            return {
                'host': this.model.get('ldapHost'),
                'port': this.model.get('ldapPort'),
                'useSsl': this.model.get('ldapSecurity'),
                'useStartTls': this.model.get('ldapSecurity'),
                'username': this.model.get('ldapUsername'),
                'password': this.model.get('ldapPassword'),
                'bindRequiresDn': this.model.get('ldapBindRequiresDn'),
                'accountDomainName': this.model.get('ldapAccountDomainName'),
                'accountDomainNameShort': this.model.get('ldapAccountDomainNameShort'),
                'accountCanonicalForm': this.model.get('ldapAccountCanonicalForm'),
            };
        },

        testConnection: function () {
            let data = this.getConnectionData();

            this.$el.find('button').prop('disabled', true);

            this.notify('Connecting', null, null, 'Settings');

            Espo.Ajax
                .postRequest('Ldap/action/testConnection', data)
                .then(() => {
                    this.$el.find('button').prop('disabled', false);

                    Espo.Ui.success(this.translate('ldapTestConnection', 'messages', 'Settings'));
                })
                .catch(xhr => {
                    let statusReason = xhr.getResponseHeader('X-Status-Reason') || '';
                    statusReason = statusReason.replace(/ $/, '');
                    statusReason = statusReason.replace(/,$/, '');

                    let msg = this.translate('Error') + ' ' + xhr.status;

                    if (statusReason) {
                        msg += ': ' + statusReason;
                    }

                    Espo.Ui.error(msg, true);

                    console.error(msg);

                    xhr.errorIsHandled = true;

                    this.$el.find('button').prop('disabled', false);
                });
        },
    });
});
PK]B!�jj2views/admin/auth-log-record/record/detail-small.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/auth-log-record/record/detail-small', ['views/record/detail-small'], function (Dep) {

    return Dep.extend({

        sideDisabled: true,

        isWide: true,

        bottomView: 'views/record/detail-bottom',
    });
});
PK]���o++,views/admin/auth-log-record/record/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/auth-log-record/record/detail', ['views/record/detail'], function (Dep) {

    return Dep.extend({

        sideDisabled: true,

        readOnly: true,
    });
});
PK]�]�J��*views/admin/auth-log-record/record/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/auth-log-record/record/list', ['views/record/list'], function (Dep) {

    return Dep.extend({

        rowActionsView: 'views/record/row-actions/view-and-remove',

        massActionList: ['remove'],

        checkAllResultMassActionList: ['remove'],
    });
});
PK]��T//,views/admin/auth-log-record/modals/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/auth-log-record/modals/detail', ['views/modals/detail'], function (Dep) {

    return Dep.extend({

        sideDisabled: true,

        editDisabled: true,
    });
});
PK]x��22#views/admin/auth-log-record/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/auth-log-record/list', ['views/list'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);
        },

        getHeader: function () {
            return this.buildHeaderHtml([
                $('<a>')
                    .attr('href', '#Admin')
                    .text(this.translate('Administration')),
                $('<span>')
                    .text(this.getLanguage().translate('Auth Log', 'labels', 'Admin')),
            ]);
        },

        updatePageTitle: function () {
            this.setPageTitle(this.getLanguage().translate('Auth Log', 'labels', 'Admin'));
        },
    });
});
PK]���%views/admin/label-manager/category.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/label-manager/category', ['view'], function (Dep) {

    return Dep.extend({

        template: 'admin/label-manager/category',

        data: function () {
            return {
                categoryDataList: this.getCategoryDataList()
            };
        },

        events: {},

        setup: function () {
            this.scope = this.options.scope;
            this.language = this.options.language;
            this.categoryData = this.options.categoryData;
        },

        getCategoryDataList: function () {
            var labelList = Object.keys(this.categoryData);

            labelList.sort((v1, v2) => {
                return v1.localeCompare(v2);
            });

            var categoryDataList = [];

            labelList.forEach(name => {
                var value = this.categoryData[name];

                if (value === null) {
                    value = '';
                }

                if (value.replace) {
                    value = value.replace(/\n/i, '\\n');
                }

                var o = {
                    name: name,
                    value: value,
                };

                var arr = name.split('[.]');

                o.label = arr.slice(1).join(' . ');

                categoryDataList.push(o);
            });

            return categoryDataList;
        },
    });
});
PK]��q�44!views/admin/label-manager/edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/label-manager/edit', ['view'], function (Dep) {

    return Dep.extend({

        template: 'admin/label-manager/edit',

        data: function () {
            return {
                categoryList: this.getCategoryList(),
                scope: this.scope
            };
        },

        events: {
            'click [data-action="showCategory"]': function (e) {
                var name = $(e.currentTarget).data('name');
                this.showCategory(name);
            },
            'click [data-action="hideCategory"]': function (e) {
                var name = $(e.currentTarget).data('name');
                this.hideCategory(name);
            },
            'click [data-action="cancel"]': function (e) {
                this.actionCancel();
            },
            'click [data-action="save"]': function (e) {
                this.actionSave();
            },
            'change input.label-value': function (e) {
                var name = $(e.currentTarget).data('name');
                var value = $(e.currentTarget).val();
                this.setLabelValue(name, value);
            }
        },

        setup: function () {
            this.scope = this.options.scope;
            this.language = this.options.language;

            this.dirtyLabelList = [];

            this.wait(true);

            Espo.Ajax.postRequest('LabelManager/action/getScopeData', {
                scope: this.scope,
                language: this.language,
            }).then(data => {
                this.scopeData = data;

                this.scopeDataInitial = Espo.Utils.cloneDeep(this.scopeData);
                this.wait(false);
            });
        },

        getCategoryList: function () {
            var categoryList = Object.keys(this.scopeData).sort((v1, v2) => {

                return v1.localeCompare(v2);
            });

            return categoryList;
        },

        setLabelValue: function (name, value) {
            var category = name.split('[.]')[0];

            value = value.replace(/\\\\n/i, '\n');

            value = value.trim();

            this.scopeData[category][name] = value;

            this.dirtyLabelList.push(name);
            this.setConfirmLeaveOut(true);

            if (!this.hasView(category)) {
                return;
            }

            this.getView(category).categoryData[name] = value;
        },

        setConfirmLeaveOut: function (value) {
            this.getRouter().confirmLeaveOut = value;
        },

        afterRender: function () {
            this.$save = this.$el.find('button[data-action="save"]');
            this.$cancel = this.$el.find('button[data-action="cancel"]');
        },

        actionSave: function () {
            this.$save.addClass('disabled').attr('disabled');
            this.$cancel.addClass('disabled').attr('disabled');

            var data = {};

            this.dirtyLabelList.forEach(name => {
                var category = name.split('[.]')[0];
                var value = this.scopeData[category][name];
                data[name] = value;
            });

            Espo.Ui.notify(this.translate('saving', 'messages'));

            Espo.Ajax.postRequest('LabelManager/action/saveLabels', {
                scope: this.scope,
                language: this.language,
                labels: data,
            })
            .then(returnData => {
                this.scopeDataInitial = Espo.Utils.cloneDeep(this.scopeData);
                this.dirtyLabelList = [];
                this.setConfirmLeaveOut(false);

                this.$save.removeClass('disabled').removeAttr('disabled');
                this.$cancel.removeClass('disabled').removeAttr('disabled');

                for (var key in returnData) {
                    var name = key.split('[.]').splice(1).join('[.]');
                    this.$el.find('input.label-value[data-name="'+name+'"]').val(returnData[key]);
                }

                Espo.Ui.success(this.translate('Saved'));

                this.getHelper().broadcastChannel.postMessage('update:language');

                this.getLanguage().loadSkipCache();
            })
            .catch(() => {
                this.$save.removeClass('disabled').removeAttr('disabled');
                this.$cancel.removeClass('disabled').removeAttr('disabled');
            });
        },

        actionCancel: function () {
            this.scopeData = Espo.Utils.cloneDeep(this.scopeDataInitial);
            this.dirtyLabelList = [];

            this.setConfirmLeaveOut(false);

            this.getCategoryList().forEach(category => {
                if (!this.hasView(category)) {
                    return;
                }

                this.getView(category).categoryData = this.scopeData[category];
                this.getView(category).reRender();
            });
        },

        showCategory: function (category) {
            this.$el.find('a[data-action="showCategory"][data-name="'+category+'"]').addClass('hidden');

            if (this.hasView(category)) {
                this.$el.find('a[data-action="hideCategory"][data-name="'+category+'"]').removeClass('hidden');
                this.$el.find('.panel-body[data-name="'+category+'"]').removeClass('hidden');

                return;
            }

            this.createView(category, 'views/admin/label-manager/category', {
                selector: '.panel-body[data-name="'+category+'"]',
                categoryData: this.getCategoryData(category),
                scope: this.scope,
                language: this.language,
            }, view => {
                this.$el.find('.panel-body[data-name="'+category+'"]').removeClass('hidden');
                this.$el.find('a[data-action="hideCategory"][data-name="'+category+'"]').removeClass('hidden');
                view.render();
            });
        },

        hideCategory: function (category) {
            this.clearView(category);

            this.$el.find('.panel-body[data-name="'+category+'"]').addClass('hidden');
            this.$el.find('a[data-action="showCategory"][data-name="'+category+'"]').removeClass('hidden');
            this.$el.find('a[data-action="hideCategory"][data-name="'+category+'"]').addClass('hidden');
        },

        getCategoryData: function (category) {
            return this.scopeData[category] || {};
        },
    });
});


PK]r:��"views/admin/label-manager/index.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';
import Select from 'ui/select';

class LabelManagerView extends  View {

    template = 'admin/label-manager/index'

    scopeList = null
    scope = null
    language = null
    languageList = null

    events = {
        /** @this LabelManagerView */
        'click [data-action="selectScope"]': function (e) {
            let scope = $(e.currentTarget).data('name');

            this.getRouter().checkConfirmLeaveOut(() => {
                this.selectScope(scope);
            });
        },
        /** @this LabelManagerView */
        'change select[data-name="language"]': function (e) {
            let language = $(e.currentTarget).val();

            this.getRouter().checkConfirmLeaveOut(() => {
                this.selectLanguage(language);
            });
        }
    }

    data() {
        return {
            scopeList: this.scopeList,
            languageList: this.languageList,
            scope: this.scope,
            language: this.language,
        };
    }

    setup() {
        this.languageList = this.getMetadata().get(['app', 'language', 'list']) || ['en_US'];

        this.languageList.sort((v1, v2) => {
            return this.getLanguage().translateOption(v1, 'language')
                .localeCompare(this.getLanguage().translateOption(v2, 'language'));
        });

        this.wait(true);

        Espo.Ajax.postRequest('LabelManager/action/getScopeList').then(scopeList => {
            this.scopeList = scopeList;

            this.scopeList.sort((v1, v2) => {
                return this.translate(v1, 'scopeNamesPlural')
                    .localeCompare(this.translate(v2, 'scopeNamesPlural'));
            });

            this.scopeList = this.scopeList.filter(scope => {
                if (scope === 'Global') {
                    return;
                }

                if (this.getMetadata().get(['scopes', scope])) {
                    if (this.getMetadata().get(['scopes', scope, 'disabled'])) {
                        return;
                    }
                }

                return true;
            });

            this.scopeList.unshift('Global');

            this.wait(false);
        });

        this.scope = this.options.scope || 'Global';
        this.language = this.options.language || this.getConfig().get('language');

        this.once('after:render', () => {
            this.selectScope(this.scope, true);
        });
    }

    afterRender() {
        Select.init(
            this.element.querySelector(`select[data-name="language"]`)
        );
    }

    selectLanguage(language) {
        this.language = language;

        if (this.scope) {
            this.getRouter().navigate(
                '#Admin/labelManager/scope=' + this.scope + '&language=' + this.language,
                {trigger: false}
            );
        } else {
            this.getRouter().navigate('#Admin/labelManager/language=' + this.language, {trigger: false});
        }

        this.createRecordView();
    }

    selectScope(scope, skipRouter) {
        this.scope = scope;

        if (!skipRouter) {
            this.getRouter().navigate('#Admin/labelManager/scope=' + scope + '&language=' + this.language,
                {trigger: false});
        }

        this.$el.find('[data-action="selectScope"]')
            .removeClass('disabled')
            .removeAttr('disabled');

        this.$el.find('[data-name="' + scope + '"][data-action="selectScope"]')
            .addClass('disabled')
            .attr('disabled', 'disabled');

        this.createRecordView();
    }

    createRecordView() {
        Espo.Ui.notify(' ... ');

        this.createView('record', 'views/admin/label-manager/edit', {
            selector: '.language-record',
            scope: this.scope,
            language: this.language,
        }, view => {
            view.render();

            Espo.Ui.notify(false);

            $(window).scrollTop(0);
        });
    }

    updatePageTitle() {
        this.setPageTitle(this.getLanguage().translate('Label Manager', 'labels', 'Admin'));
    }
}

export default LabelManagerView;
PK]z+o�aa)views/admin/layouts/bottom-panels-edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/layouts/bottom-panels-edit', ['views/admin/layouts/bottom-panels-detail'], function (Dep) {

    return Dep.extend({

        hasStream: false,

        hasRelationships: false,

        viewType: 'edit',
    });
});
PK]!^�R�0�0+views/admin/layouts/bottom-panels-detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/layouts/bottom-panels-detail', ['views/admin/layouts/side-panels-detail'], function (Dep) {

    return Dep.extend({

        hasStream: true,

        hasRelationships: true,

        TAB_BREAK_KEY: '_tabBreak_{n}',

        setup: function () {
            Dep.prototype.setup.call(this);

            this.on('update-item', (name, attributes) => {


                if (this.isTabName(name)) {
                    let $li = $("#layout ul > li[data-name='" + name + "']");

                    $li.find('.left > span')
                        .text(this.composeTabBreakLabel(attributes));
                }
            });
        },

        composeTabBreakLabel: function (item) {
            let label = '. . . ' + this.translate('tabBreak', 'fields', 'LayoutManager');

            if (item.tabLabel) {
                label += ' : ' + item.tabLabel;
            }

            return label;
        },

        readDataFromLayout: function (layout) {
            let panelListAll = [];
            let labels = {};
            let params = {};

            layout = Espo.Utils.cloneDeep(layout);

            if (
                this.hasStream &&
                this.getMetadata().get(['scopes', this.scope, 'stream'])
            ) {
                panelListAll.push('stream');

                labels['stream'] = this.translate('Stream');

                params['stream'] = {
                    name: 'stream',
                    sticked: true,
                    index: 2,
                };
            }

            (this.getMetadata()
                .get(['clientDefs', this.scope, 'bottomPanels', this.viewType]) || []
            ).forEach(item => {
                if (!item.name) {
                    return;
                }

                panelListAll.push(item.name);

                if (item.label) {
                    labels[item.name] = item.label;
                }

                params[item.name] = Espo.Utils.clone(item);

                if ('order' in item) {
                    params[item.name].index = item.order;
                }
            });

            for (let name in layout) {
                let item = layout[name];

                if (item.tabBreak) {
                    panelListAll.push(name);

                    labels[name] = this.composeTabBreakLabel(item);

                    params[name] = {
                        name: item.name,
                        index: item.index,
                        tabBreak: true,
                        tabLabel: item.tabLabel || null,
                    };
                }
            }

            this.links = {};

            if (this.hasRelationships) {
                var linkDefs = this.getMetadata().get(['entityDefs', this.scope, 'links']) || {};

                Object.keys(linkDefs).forEach(link => {
                    if (
                        linkDefs[link].disabled ||
                        linkDefs[link].utility ||
                        linkDefs[link].layoutRelationshipsDisabled
                    ) {
                        return;
                    }

                    if (!~['hasMany', 'hasChildren'].indexOf(linkDefs[link].type)) {
                        return;
                    }

                    panelListAll.push(link);

                    labels[link] = this.translate(link, 'links', this.scope);

                    var item = {
                        name: link,
                        index: 5,
                    };

                    this.dataAttributeList.forEach(attribute => {
                        if (attribute in item) {
                            return;
                        }

                        var value = this.getMetadata()
                            .get(['clientDefs', this.scope, 'relationshipPanels', item.name, attribute]);

                        if (value === null) {
                            return;
                        }

                        item[attribute] = value;
                    });

                    this.links[link] = true;

                    params[item.name] = item;
                });
            }

            this.disabledFields = [];

            layout = layout || {};

            this.rowLayout = [];

            panelListAll = panelListAll.sort((v1, v2) => {
                return params[v1].index - params[v2].index
            });

            panelListAll.push('_delimiter_');

            if (!layout['_delimiter_']) {
                layout['_delimiter_'] = {
                    disabled: true,
                };
            }

            labels[this.TAB_BREAK_KEY] = '. . . ' + this.translate('tabBreak', 'fields', 'LayoutManager');

            panelListAll.push(this.TAB_BREAK_KEY);

            panelListAll.forEach((item, index) => {
                var disabled = false;
                var itemData = layout[item] || {};

                if (itemData.disabled) {
                    disabled = true;
                }

                if (!layout[item]) {
                    if ((params[item] || {}).disabled) {
                        disabled = true;
                    }
                }

                if (this.links[item]) {
                    if (!layout[item]) {
                        disabled = true;
                    }
                }

                if (item === this.TAB_BREAK_KEY) {
                    disabled = true;
                }

                var labelText;

                if (labels[item]) {
                    labelText = this.getLanguage().translate(labels[item], 'labels', this.scope);
                } else {
                    labelText = this.getLanguage().translate(item, 'panels', this.scope);
                }

                if (disabled) {
                    let o = {
                        name: item,
                        label: labelText,
                    };

                    if (o.name[0] === '_') {
                        if (o.name === '_delimiter_') {
                            o.notEditable = true;
                            o.label = '. . .';
                        }
                    }

                    this.disabledFields.push(o);

                    return;
                }

                var o = {
                    name: item,
                    label: labelText,
                };

                if (o.name[0] === '_') {
                    if (o.name === '_delimiter_') {
                        o.notEditable = true;
                        o.label = '. . .';
                    }
                }

                if (o.name in params) {
                    this.dataAttributeList.forEach(attribute => {
                        if (attribute === 'name') {
                            return;
                        }

                        var itemParams = params[o.name] || {};

                        if (attribute in itemParams) {
                            o[attribute] = itemParams[attribute];
                        }
                    });
                }

                for (var i in itemData) {
                    o[i] = itemData[i];
                }

                o.index = ('index' in itemData) ? itemData.index : index;

                this.rowLayout.push(o);

                this.itemsData[o.name] = Espo.Utils.cloneDeep(o);
            });

            this.rowLayout.sort((v1, v2) => {
                return (v1.index || 0) - (v2.index || 0);
            });
        },

        onDrop: function () {
            let tabBreakIndex = -1;

            let $tabBreak = null;

            this.$el.find('ul.enabled').children().each((i, li) => {
                let $li = $(li);
                let name = $li.attr('data-name');

                if (this.isTabName(name)) {
                    if (name !== this.TAB_BREAK_KEY) {
                        let itemIndex = parseInt(name.split('_')[2]);

                        if (itemIndex > tabBreakIndex) {
                            tabBreakIndex = itemIndex;
                        }
                    }
                }
            });

            tabBreakIndex++;

            this.$el.find('ul.enabled').children().each((i, li) => {
                let $li = $(li);
                let name = $li.attr('data-name');

                if (this.isTabName(name) && name === this.TAB_BREAK_KEY) {
                    $tabBreak = $li.clone();

                    let realName = this.TAB_BREAK_KEY.slice(0, -3) + tabBreakIndex;

                    $li.attr('data-name', realName);

                    delete this.itemsData[realName];
                }
            });

            if (!$tabBreak) {
                this.$el.find('ul.disabled').children().each((i, li) => {
                    let $li = $(li);

                    let name = $li.attr('data-name');

                    if (this.isTabName(name) && name !== this.TAB_BREAK_KEY) {
                        $li.remove();
                    }
                });
            }

            if ($tabBreak) {
                $tabBreak.appendTo(this.$el.find('ul.disabled'));
            }
        },

        isTabName: function (name) {
            return name.substring(0, this.TAB_BREAK_KEY.length - 3) === this.TAB_BREAK_KEY.slice(0, -3);
        },

        getEditAttributesModalViewOptions: function (attributes) {
            let options = Dep.prototype.getEditAttributesModalViewOptions.call(this, attributes);

            if (this.isTabName(attributes.name)) {
                options.attributeList = [
                    'tabLabel',
                ];

                options.attributeDefs = {
                    tabLabel: {
                        type: 'varchar',
                    },
                };
            }

            return options;
        },

        fetch: function () {
            let layout = Dep.prototype.fetch.call(this);

            let newLayout = {};


            for (let name in layout) {
                if (layout[name].disabled && this.links[name]) {
                    continue;
                }

                newLayout[name] = layout[name];

                if (this.isTabName(name) && name !== this.TAB_BREAK_KEY /*&& this.itemsData[name]*/) {
                    let data = this.itemsData[name] || {};

                    newLayout[name].tabBreak = true;
                    newLayout[name].tabLabel = data.tabLabel;
                }
                else {
                   delete newLayout[name].tabBreak;
                   delete newLayout[name].tabLabel;
                }
            }

            delete newLayout[this.TAB_BREAK_KEY];

            return newLayout;
        },
    });
});
PK]�gi��"views/admin/layouts/mass-update.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/layouts/mass-update', ['views/admin/layouts/rows'], function (Dep) {

    return Dep.extend({

        dataAttributeList: ['name'],

        editable: false,

        ignoreList: [],

        ignoreTypeList: ['duration'],

        dataAttributesDefs: {
            name: {
                readOnly: true
            }
        },

        setup: function () {
            Dep.prototype.setup.call(this);

            this.wait(true);

            this.loadLayout(() => {
                this.wait(false);
            });
        },

        loadLayout: function (callback) {
            this.getModelFactory().create(this.scope, (model) => {
                this.getHelper().layoutManager.getOriginal(this.scope, this.type, this.setId, (layout) => {

                    var allFields = [];

                    for (let field in model.defs.fields) {
                        if (
                            !model.getFieldParam(field, 'readOnly') &&
                            this.isFieldEnabled(model, field)
                        ) {
                            allFields.push(field);
                        }
                    }

                    allFields.sort((v1, v2) => {
                        return this.translate(v1, 'fields', this.scope)
                            .localeCompare(this.translate(v2, 'fields', this.scope));
                    });

                    this.enabledFieldsList = [];

                    this.enabledFields = [];
                    this.disabledFields = [];

                    for (let i in layout) {
                        this.enabledFields.push({
                            name: layout[i],
                            label: this.getLanguage().translate(layout[i], 'fields', this.scope),
                        });

                        this.enabledFieldsList.push(layout[i]);
                    }

                    for (let i in allFields) {
                        if (!_.contains(this.enabledFieldsList, allFields[i])) {
                            this.disabledFields.push({
                                name: allFields[i],
                                label: this.getLanguage().translate(allFields[i], 'fields', this.scope),
                            });
                        }
                    }

                    this.rowLayout = this.enabledFields;

                    for (let i in this.rowLayout) {
                        this.rowLayout[i].label = this.getLanguage()
                            .translate(this.rowLayout[i].name, 'fields', this.scope);

                        this.itemsData[this.rowLayout[i].name] = Espo.Utils.cloneDeep(this.rowLayout[i]);
                    }

                    callback();
                });
            });
        },

        fetch: function () {
            var layout = [];

            $("#layout ul.enabled > li").each((i, el) => {
                layout.push($(el).data('name'));
            });

            return layout;
        },

        validate: function () {
            return true;
        },

        isFieldEnabled: function (model, name) {
            if (this.ignoreList.indexOf(name) !== -1) {
                return false;
            }

            if (this.ignoreTypeList.indexOf(model.getFieldParam(name, 'type')) !== -1) {
                return false;
            }

            var layoutList = model.getFieldParam(name, 'layoutAvailabilityList');

            if (layoutList && !~layoutList.indexOf(this.type)) {
                return;
            }

            return !model.getFieldParam(name, 'disabled') &&
                !model.getFieldParam(name, 'utility') &&
                !model.getFieldParam(name, 'layoutMassUpdateDisabled') &&
                !model.getFieldParam(name, 'readOnly');
        },
    });
});
PK]��T++/views/admin/layouts/side-panels-detail-small.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/layouts/side-panels-detail-small', ['views/admin/layouts/side-panels-detail'], function (Dep) {

    return Dep.extend({

        viewType: 'detailSmall',
    });
});
PK]�N��

#views/admin/layouts/detail-small.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/layouts/detail-small', ['views/admin/layouts/detail'], function (Dep) {

    return Dep.extend({

        columnCount: 2,
    });
});
PK]|��))/views/admin/layouts/bottom-panels-edit-small.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/layouts/bottom-panels-edit-small', ['views/admin/layouts/bottom-panels-edit'], function (Dep) {

    return Dep.extend({

        viewType: 'editSmall',
    });
});
PK]#�iAaaviews/admin/layouts/kanban.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/layouts/kanban', ['views/admin/layouts/list'], function (Dep) {

    return Dep.extend({

        dataAttributeList: ['name', 'link', 'align', 'view', 'isLarge'],

        dataAttributesDefs: {
            link: {type: 'bool'},
            isLarge: {type: 'bool'},
            width: {type: 'float'},
            align: {
                type: 'enum',
                options: ["left", "right"]
            },
            view: {
                type: 'varchar',
                readOnly: true
            },
            name: {
                type: 'varchar',
                readOnly: true
            }
        },

        editable: true,

        ignoreList: [],

        ignoreTypeList: [],
    });
});
PK]���!!#views/admin/layouts/default-page.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';

class LayoutDefaultPageView extends View {

    // language=Handlebars
    templateContent = `
        <div class="margin-bottom">{{translate 'selectLayout' category='messages' scope='Admin'}}</div>
        <div class="button-container">
            <button data-action="createLayout" class="btn btn-link">{{translate 'Create'}}</button>
        </div>
    `
}

export default LayoutDefaultPageView;
PK]�Lk�%%)views/admin/layouts/side-panels-detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/layouts/side-panels-detail', ['views/admin/layouts/rows'], function (Dep) {

    return Dep.extend({

        dataAttributeList: [
            'name',
            'dynamicLogicVisible',
            'style',
            'dynamicLogicStyled',
            'sticked',
        ],

        dataAttributesDefs: {
            dynamicLogicVisible: {
                type: 'base',
                view: 'views/admin/field-manager/fields/dynamic-logic-conditions',
                tooltip: 'dynamicLogicVisible',
            },
            style: {
                type: 'enum',
                options: [
                    'default',
                    'success',
                    'danger',
                    'warning',
                ],
                default: 'default',
                translation: 'LayoutManager.options.style',
                tooltip: 'panelStyle',
            },
            dynamicLogicStyled: {
                type: 'base',
                view: 'views/admin/field-manager/fields/dynamic-logic-conditions',
                tooltip: 'dynamicLogicStyled',
            },
            sticked: {
                type: 'bool',
                tooltip: 'sticked',
            },
            name: {
                readOnly: true,
            },
        },

        dataAttributesDynamicLogicDefs: {
            fields: {
                dynamicLogicStyled: {
                    visible: {
                        conditionGroup: [
                            {
                                type: 'and',
                                value: [
                                    {
                                        attribute: 'style',
                                        type: 'notEquals',
                                        value: 'default',
                                    },
                                    {
                                        attribute: 'style',
                                        type: 'isNotEmpty',
                                    },
                                ]
                            }

                        ]
                    }
                },
            }
        },

        editable: true,

        ignoreList: [],

        ignoreTypeList: [],

        viewType: 'detail',

        setup: function () {
            Dep.prototype.setup.call(this);

            this.dataAttributesDefs = Espo.Utils.cloneDeep(this.dataAttributesDefs);

            this.dataAttributesDefs.dynamicLogicVisible.scope = this.scope;
            this.dataAttributesDefs.dynamicLogicStyled.scope = this.scope;

            this.wait(true);

            this.loadLayout(() => {
                this.wait(false);
            });
        },

        loadLayout: function (callback) {
            this.getHelper().layoutManager.getOriginal(this.scope, this.type, this.setId, (layout) => {
                this.readDataFromLayout(layout);

                if (callback) {
                    callback();
                }
            });
        },

        readDataFromLayout: function (layout) {
            var panelListAll = [];
            var labels = {};
            var params = {};

            layout = Espo.Utils.cloneDeep(layout);

            if (
                this.getMetadata().get(['clientDefs', this.scope, 'defaultSidePanel', this.viewType]) !== false &&
                !this.getMetadata().get(['clientDefs', this.scope, 'defaultSidePanelDisabled'])
            ) {
                panelListAll.push('default');

                labels['default'] = 'Default';
            }

            (this.getMetadata().get(['clientDefs', this.scope, 'sidePanels', this.viewType]) || [])
                .forEach(item => {
                    if (!item.name) {
                        return;
                    }

                    panelListAll.push(item.name);

                    if (item.label) {
                        labels[item.name] = item.label;
                    }
                    params[item.name] = item;
                });

            this.disabledFields = [];

            layout = layout || {};

            this.rowLayout = [];

            panelListAll.push('_delimiter_');

            if (!layout['_delimiter_']) {
                layout['_delimiter_'] = {
                    disabled: true,
                };
            }

            panelListAll.forEach((item, index) => {
                let disabled = false;
                let itemData = layout[item] || {};

                if (itemData.disabled) {
                    disabled = true;
                }

                if (!layout[item]) {
                    if ((params[item] || {}).disabled) {
                        disabled = true;
                    }
                }

                var labelText;

                if (labels[item]) {
                    labelText = this.getLanguage().translate(labels[item], 'labels', this.scope);
                } else {
                    labelText = this.getLanguage().translate(item, 'panels', this.scope);
                }

                if (disabled) {
                    let o = {
                        name: item,
                        label: labelText,
                    };

                    if (o.name[0] === '_') {
                        o.notEditable = true;

                        if (o.name === '_delimiter_') {
                            o.label = '. . .';
                        }
                    }

                    this.disabledFields.push(o);

                    return;
                }

                let o = {
                    name: item,
                    label: labelText,
                };

                if (o.name[0] === '_') {
                    o.notEditable = true;
                    if (o.name === '_delimiter_') {
                        o.label = '. . .';
                    }
                }

                if (o.name in params) {
                    this.dataAttributeList.forEach(attribute => {
                        if (attribute === 'name') {
                            return;
                        }

                        var itemParams = params[o.name] || {};

                        if (attribute in itemParams) {
                            o[attribute] = itemParams[attribute];
                        }
                    });
                }

                for (var i in itemData) {
                    o[i] = itemData[i];
                }

                o.index = ('index' in itemData) ? itemData.index : index;

                this.rowLayout.push(o);

                this.itemsData[o.name] = Espo.Utils.cloneDeep(o);
            });

            this.rowLayout.sort((v1, v2) => {
                return v1.index - v2.index;
            });
        },

        fetch: function () {
            let layout = {};

            $('#layout ul.disabled > li').each((i, el) => {
                var name = $(el).attr('data-name');

                layout[name] = {
                    disabled: true,
                };
            });

            $('#layout ul.enabled > li').each((i, el) => {
                let $el = $(el);
                let o = {};

                let name = $el.attr('data-name');

                let attributes = this.itemsData[name] || {};

                attributes.name = name;

                this.dataAttributeList.forEach(attribute => {
                    if (attribute === 'name') {
                        return;
                    }

                    if (attribute in attributes) {
                        o[attribute] = attributes[attribute];
                    }
                });

                o.index = i;

                layout[name] = o;
            })

            return layout;
        },
    });
});
PK]n�u6''-views/admin/layouts/side-panels-edit-small.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/layouts/side-panels-edit-small', ['views/admin/layouts/side-panels-detail'], function (Dep) {

    return Dep.extend({

        viewType: 'editSmall',
    });
});
PK]��11$views/admin/layouts/relationships.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/**
 * @deprecated
 */
define('views/admin/layouts/relationships', ['views/admin/layouts/rows'], function (Dep) {

    return Dep.extend({

        dataAttributeList: [
            'name',
            'dynamicLogicVisible',
            'style',
            'dynamicLogicStyled',
        ],

        editable: true,

        dataAttributesDefs: {
            style: {
                type: 'enum',
                options: [
                    'default',
                    'success',
                    'danger',
                    'warning',
                ],
                translation: 'LayoutManager.options.style',
            },
            dynamicLogicVisible: {
                type: 'base',
                view: 'views/admin/field-manager/fields/dynamic-logic-conditions',
                tooltip: 'dynamicLogicVisible',
            },
            dynamicLogicStyled: {
                type: 'base',
                view: 'views/admin/field-manager/fields/dynamic-logic-conditions',
                tooltip: 'dynamicLogicStyled',
            },
            name: {
                readOnly: true,
            },
        },

        languageCategory: 'links',

        setup: function () {
            Dep.prototype.setup.call(this);

            this.dataAttributesDefs = Espo.Utils.cloneDeep(this.dataAttributesDefs);

            this.dataAttributesDefs.dynamicLogicVisible.scope = this.scope;
            this.dataAttributesDefs.dynamicLogicStyled.scope = this.scope;

            this.wait(true);

            this.loadLayout(() => {
                this.wait(false);
            });
        },

        loadLayout: function (callback) {
            this.getModelFactory().create(this.scope, (model) => {
                this.getHelper().layoutManager.getOriginal(this.scope, this.type, this.setId, (layout) => {

                    let allFields = [];

                    for (let field in model.defs.links) {
                        if (['hasMany', 'hasChildren'].indexOf(model.defs.links[field].type) !== -1) {
                            if (this.isLinkEnabled(model, field)) {
                                allFields.push(field);
                            }
                        }
                    }

                    allFields.sort((v1, v2) => {
                        return this.translate(v1, 'links', this.scope)
                            .localeCompare(this.translate(v2, 'links', this.scope));
                    });

                    allFields.push('_delimiter_');

                    this.enabledFieldsList = [];

                    this.enabledFields = [];
                    this.disabledFields = [];

                    for (let i in layout) {
                        let item = layout[i];
                        let o;

                        if (typeof item == 'string' || item instanceof String) {
                            o = {
                                name: item,

                                label: this.getLanguage().translate(item, 'links', this.scope)
                            };
                        }
                        else {
                            o = item;

                            o.label = this.getLanguage().translate(o.name, 'links', this.scope);
                        }

                        if (o.name[0] === '_') {
                            o.notEditable = true;

                            if (o.name === '_delimiter_') {
                                o.label = '. . .';
                            }
                        }

                        this.dataAttributeList.forEach(attribute => {
                            if (attribute === 'name') {
                                return;
                            }

                            if (attribute in o) {
                                return;
                            }

                            var value = this.getMetadata()
                                .get(['clientDefs', this.scope, 'relationshipPanels', o.name, attribute]);

                            if (value === null) {
                                return;
                            }

                            o[attribute] = value;
                        });

                        this.enabledFields.push(o);
                        this.enabledFieldsList.push(o.name);
                    }

                    for (let i in allFields) {
                        if (!_.contains(this.enabledFieldsList, allFields[i])) {
                            var name = allFields[i];

                            var label = this.getLanguage().translate(name, 'links', this.scope);

                            let o = {
                                name: name,
                                label: label,
                            };

                            if (o.name[0] === '_') {
                                o.notEditable = true;

                                if (o.name === '_delimiter_') {
                                    o.label = '. . .';
                                }
                            }

                            this.disabledFields.push(o);
                        }
                    }

                    this.rowLayout = this.enabledFields;

                    for (let i in this.rowLayout) {
                        let o = this.rowLayout[i];

                        o.label = this.getLanguage().translate(this.rowLayout[i].name, 'links', this.scope);

                        if (o.name === '_delimiter_') {
                            o.label = '. . .';
                        }

                        this.itemsData[this.rowLayout[i].name] = Espo.Utils.cloneDeep(this.rowLayout[i]);
                    }

                    callback();
                });
            });
        },

        validate: function () {
            return true;
        },

        isLinkEnabled: function (model, name) {
            return !model.getLinkParam(name, 'disabled') &&
                !model.getLinkParam(name, 'utility') &&
                !model.getLinkParam(name, 'layoutRelationshipsDisabled');
        },
    });
});
PK]���!views/admin/layouts/list-small.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/layouts/list-small', ['views/admin/layouts/list'], function (Dep) {

    return Dep.extend({});
});
PK]ϊ�� � views/admin/layouts/base.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module module:views/admin/layouts/base */

import View from 'view';

class LayoutBaseView extends View {

    /**
     * @type {string}
     */
    scope
    /**
     * @type {string}
     */
    type

    events = {
        /** @this LayoutBaseView */
        'click button[data-action="save"]': function () {
            this.actionSave();
        },
        /** @this LayoutBaseView */
        'click button[data-action="cancel"]': function () {
            this.cancel();
        },
        /** @this LayoutBaseView */
        'click button[data-action="resetToDefault"]': function () {
            this.confirm(this.translate('confirmation', 'messages'), () => {
                this.resetToDefault();
            });
        },
        /** @this LayoutBaseView */
        'click button[data-action="remove"]': function () {
            this.actionDelete();
        },
    }

    buttonList = [
        {
            name: 'save',
            label: 'Save',
            style: 'primary',
        },
        {
            name: 'cancel',
            label: 'Cancel',
        },
    ]

    // noinspection JSUnusedGlobalSymbols
    dataAttributes = null
    dataAttributesDefs = null
    dataAttributesDynamicLogicDefs = null

    setup() {
        this.buttonList = _.clone(this.buttonList);
        this.events = _.clone(this.events);
        this.scope = this.options.scope;
        this.type = this.options.type;
        this.setId = this.options.setId;
        this.em = this.options.em;

        let defs = this.getMetadata()
            .get(['clientDefs', this.scope, 'additionalLayouts', this.type]) ?? {};

        this.typeDefs = defs;

        this.dataAttributeList = Espo.Utils.clone(defs.dataAttributeList || this.dataAttributeList);

        this.isCustom = !!defs.isCustom;

        if (this.isCustom && this.em) {
            this.buttonList.push({
                name: 'remove',
                label: 'Remove',
            })
        }

        if (!this.isCustom) {
            this.buttonList.push({
                name: 'resetToDefault',
                label: 'Reset to Default',
            });
        }
    }

    actionSave() {
        this.disableButtons();
        Espo.Ui.notify(this.translate('saving', 'messages'));

        this.save(this.enableButtons.bind(this));
    }

    disableButtons() {
        this.$el.find('.button-container button').attr('disabled', 'disabled');
    }

    enableButtons() {
        this.$el.find('.button-container button').removeAttr('disabled');
    }

    setConfirmLeaveOut(value) {
        this.getRouter().confirmLeaveOut = value;
    }

    setIsChanged() {
        this.isChanged = true;
        this.setConfirmLeaveOut(true);
    }

    setIsNotChanged() {
        this.isChanged = false;
        this.setConfirmLeaveOut(false);
    }

    save(callback) {
        var layout = this.fetch();

        if (!this.validate(layout)) {
            this.enableButtons();

            return false;
        }

        this.getHelper()
            .layoutManager
            .set(this.scope, this.type, layout, () => {
                Espo.Ui.success(this.translate('Saved'));

                this.setIsNotChanged();

                if (typeof callback === 'function') {
                    callback();
                }

                this.getHelper().broadcastChannel.postMessage('update:layout');
            }, this.setId)
            .catch(() => this.enableButtons());
    }

    resetToDefault() {
        this.getHelper().layoutManager.resetToDefault(this.scope, this.type, () => {
            this.loadLayout(() => {
                this.setIsNotChanged();

                this.prepareLayout().then(() => this.reRender());
            });

        }, this.options.setId);
    }

    prepareLayout() {
        return Promise.resolve();
    }

    reset() {
        this.render();
    }

    fetch() {}

    unescape(string) {
        if (string === null) {
            return '';
        }

        var map = {
            '&amp;': '&',
            '&lt;': '<',
            '&gt;': '>',
            '&quot;': '"',
            '&#x27;': "'",
        };

        var reg = new RegExp('(' + _.keys(map).join('|') + ')', 'g');

        return ('' + string).replace(reg, match => {
            return map[match];
        });
    }

    getEditAttributesModalViewOptions(attributes) {
        return {
            name: attributes.name,
            scope: this.scope,
            attributeList: this.dataAttributeList,
            attributeDefs: this.dataAttributesDefs,
            dynamicLogicDefs: this.dataAttributesDynamicLogicDefs,
            attributes: attributes,
            languageCategory: this.languageCategory,
            headerText: ' ',
        };
    }

    openEditDialog(attributes) {
        let name = attributes.name;

        let viewOptions = this.getEditAttributesModalViewOptions(attributes);

        this.createView('editModal', 'views/admin/layouts/modals/edit-attributes', viewOptions, view => {
            view.render();

            this.listenToOnce(view, 'after:save', attributes => {
                this.trigger('update-item', name, attributes);

                let $li = $("#layout ul > li[data-name='" + name + "']");

                for (let key in attributes) {
                    $li.attr('data-' + key, attributes[key]);
                    $li.data(key, attributes[key]);
                    $li.find('.' + key + '-value').text(attributes[key]);
                }

                view.close();

                this.setIsChanged();
            });
        });
    }

    cancel() {
        this.loadLayout(() => {
            this.setIsNotChanged();

            if (this.em) {
                this.trigger('cancel');

                return;
            }

            this.prepareLayout().then(() => this.reRender());
        });
    }

    // noinspection JSUnusedLocalSymbols
    validate(layout) {
        return true;
    }

    actionDelete() {
        this.confirm(this.translate('confirmation', 'messages'))
            .then(() => {
                this.disableButtons();

                Espo.Ui.notify(' ... ');

                Espo.Ajax
                    .postRequest('Layout/action/delete', {
                        scope: this.scope,
                        name: this.type,
                    })
                    .then(() => {
                        Espo.Ui.success(this.translate('Removed'), {suppress: true});

                        this.trigger('after-delete');
                    })
                    .catch(() => {
                        this.enableButtons();
                    });
            });
    }
}

export default LayoutBaseView;
PK]p:�>'views/admin/layouts/side-panels-edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/layouts/side-panels-edit', ['views/admin/layouts/side-panels-detail'], function (Dep) {

    return Dep.extend({

        viewType: 'edit',
    });
});
PK]BY��0�0views/admin/layouts/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/layouts/detail', ['views/admin/layouts/grid'], function (Dep) {

    return Dep.extend({

        dataAttributeList: [
            'name',
            'fullWidth',
            'customLabel',
            'noLabel',
        ],

        panelDataAttributeList: [
            'panelName',
            'dynamicLogicVisible',
            'style',
            'dynamicLogicStyled',
            'tabBreak',
            'tabLabel',
            'hidden',
        ],

        dataAttributesDefs: {
            fullWidth: {
                type: 'bool',
            },
            name: {
                readOnly: true,
            },
            label: {
                type: 'varchar',
                readOnly: true,
            },
            customLabel: {
                type: 'varchar',
                readOnly: true,
            },
            noLabel: {
                type: 'bool',
                readOnly: true,
            },
        },

        panelDataAttributesDefs: {
            panelName: {
                type: 'varchar',
            },
            style: {
                type: 'enum',
                options: [
                    'default',
                    'success',
                    'danger',
                    'warning'
                ],
                default: 'default',
                translation: 'LayoutManager.options.style',
                tooltip: 'panelStyle',
            },
            dynamicLogicVisible: {
                type: 'base',
                view: 'views/admin/field-manager/fields/dynamic-logic-conditions'
            },
            dynamicLogicStyled: {
                type: 'base',
                view: 'views/admin/field-manager/fields/dynamic-logic-conditions',
                tooltip: 'dynamicLogicStyled',
            },
            hidden: {
                type: 'bool',
                tooltip: 'hiddenPanel',
            },
            tabBreak: {
                type: 'bool',
                tooltip: 'tabBreak',
            },
            tabLabel: {
                type: 'varchar',
            },
        },

        defaultPanelFieldList: [
            'modifiedAt',
            'createdAt',
            'modifiedBy',
            'createdBy',
        ],

        panelDynamicLogicDefs: {
            fields: {
                tabLabel: {
                    visible: {
                        conditionGroup: [
                            {
                                attribute: 'tabBreak',
                                type: 'isTrue',
                            }
                        ]
                    }
                },
                dynamicLogicStyled: {
                    visible: {
                        conditionGroup: [
                            {
                                attribute: 'style',
                                type: 'notEquals',
                                value: 'default'
                            }
                        ]
                    }
                },
            }
        },

        setup: function () {
            Dep.prototype.setup.call(this);

            this.panelDataAttributesDefs = Espo.Utils.cloneDeep(this.panelDataAttributesDefs);

            this.panelDataAttributesDefs.dynamicLogicVisible.scope = this.scope;
            this.panelDataAttributesDefs.dynamicLogicStyled.scope = this.scope;

            this.wait(true);

            this.loadLayout(() => {
                this.setupPanels();
                this.wait(false);
            });
        },

        loadLayout: function (callback) {
            var layout;
            var model;

            var promiseList = [];

            promiseList.push(
                new Promise(resolve => {
                    this.getModelFactory().create(this.scope, (m) => {
                        this.getHelper()
                            .layoutManager
                            .getOriginal(this.scope, this.type, this.setId, (layoutLoaded) => {
                                layout = layoutLoaded;
                                model = m;
                                resolve();
                            });
                    });
                })
            );

            if (['detail', 'detailSmall'].includes(this.type)) {
                promiseList.push(
                    new Promise(resolve => {
                        this.getHelper().layoutManager.getOriginal(
                            this.scope, 'sidePanels' + Espo.Utils.upperCaseFirst(this.type),
                            this.setId,
                            layoutLoaded => {
                                this.sidePanelsLayout = layoutLoaded;

                                resolve();
                            }
                        );
                    })
                );
            }

            promiseList.push(
                new Promise(resolve => {
                    if (this.getMetadata().get(['clientDefs', this.scope, 'layoutDefaultSidePanelDisabled'])) {
                        resolve();

                        return;
                    }

                    if (this.typeDefs.allFields) {
                        resolve();

                        return;
                    }

                    this.getHelper().layoutManager.getOriginal(
                        this.scope,
                        'defaultSidePanel',
                        this.setId,
                        layoutLoaded => {
                            this.defaultPanelFieldList = Espo.Utils.clone(this.defaultPanelFieldList);

                            layoutLoaded.forEach(item => {
                                var field = item.name;

                                if (!field) {
                                    return;
                                }

                                if (field === ':assignedUser') {
                                    field = 'assignedUser';
                                }

                                if (!this.defaultPanelFieldList.includes(field)) {
                                    this.defaultPanelFieldList.push(field);
                                }
                            });

                            resolve();
                        }
                    );
                })
            );

            Promise.all(promiseList).then(() => {
                this.readDataFromLayout(model, layout);

                if (callback) {
                    callback();
                }
            });
        },

        readDataFromLayout: function (model, layout) {
            var allFields = [];

            for (var field in model.defs.fields) {
                if (this.isFieldEnabled(model, field)) {
                    allFields.push(field);
                }
            }

            this.enabledFields = [];
            this.disabledFields = [];

            this.panels = layout;

            layout.forEach((panel) => {
                panel.rows.forEach((row) => {
                    row.forEach((cell, i) => {
                        this.enabledFields.push(cell.name);
                    });
                });
            });

            allFields.sort((v1, v2) => {
                return this.translate(v1, 'fields', this.scope)
                    .localeCompare(this.translate(v2, 'fields', this.scope));
            });

            for (var i in allFields) {
                if (!_.contains(this.enabledFields, allFields[i])) {
                    this.disabledFields.push(allFields[i]);
                }
            }
        },

        isFieldEnabled: function (model, name) {
            if (this.hasDefaultPanel()) {
                if (this.defaultPanelFieldList.includes(name)) {
                    return false;
                }
            }

            var layoutList = model.getFieldParam(name, 'layoutAvailabilityList');

            if (layoutList && !layoutList.includes(this.type)) {
                return;
            }

            return !model.getFieldParam(name, 'disabled') &&
                !model.getFieldParam(name, 'utility') &&
                !model.getFieldParam(name, 'layoutDetailDisabled');
        },

        hasDefaultPanel: function () {
            if (this.getMetadata().get(['clientDefs', this.scope, 'defaultSidePanel', this.viewType]) === false) {
                return false;
            }

            if (this.getMetadata().get(['clientDefs', this.scope, 'defaultSidePanelDisabled'])) {
                return false;
            }

            if (this.sidePanelsLayout) {
                for (var name in this.sidePanelsLayout) {
                    if (name === 'default' && this.sidePanelsLayout[name].disabled) {
                        return false;
                    }
                }
            }

            return true;
        },

        validate: function (layout) {
            if (!Dep.prototype.validate.call(this, layout)) {
                return false;
            }

            let fieldList = [];

            layout.forEach(panel => {
                panel.rows.forEach(row => {
                    row.forEach(cell => {
                        if (cell !== false && cell !== null) {
                            if (cell.name) {
                                fieldList.push(cell.name);
                            }
                        }
                    });
                });
            });

            let incompatibleFieldList = [];

            let isIncompatible = false;

            fieldList.forEach(field => {
                if (isIncompatible) {
                    return;
                }

                let defs = this.getMetadata().get(['entityDefs', this.scope, 'fields', field]) || {};

                let targetFieldList = defs.detailLayoutIncompatibleFieldList || [];

                targetFieldList.forEach(itemField => {
                    if (isIncompatible) {
                        return;
                    }

                    if (~fieldList.indexOf(itemField)) {
                        isIncompatible = true;

                        incompatibleFieldList = [field].concat(targetFieldList);
                    }
                });
            });

            if (isIncompatible) {
                Espo.Ui.error(
                    this.translate('fieldsIncompatible', 'messages', 'LayoutManager')
                        .replace(
                            '{fields}',
                            incompatibleFieldList
                                .map(field => this.translate(field, 'fields', this.scope))
                                .join(', ')
                        )
                );

                return false;
            }

            return true;
        },
    });
});
PK]�vtϜ�views/admin/layouts/filters.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/layouts/filters', ['views/admin/layouts/rows'], function (Dep) {

    return Dep.extend({

        dataAttributeList: ['name'],

        editable: false,

        ignoreList: [],

        setup: function () {
            Dep.prototype.setup.call(this);

            this.wait(true);

            this.loadLayout(() => {
                this.wait(false);
            });
        },

        loadLayout: function (callback) {
            this.getModelFactory().create(this.scope, (model) => {
                this.getHelper().layoutManager.getOriginal(this.scope, this.type, this.setId, (layout) => {

                    let allFields = [];

                    for (let field in model.defs.fields) {
                        if (
                            this.checkFieldType(model.getFieldParam(field, 'type')) &&
                            this.isFieldEnabled(model, field)
                        ) {
                            allFields.push(field);
                        }
                    }

                    allFields.sort((v1, v2) => {
                        return this.translate(v1, 'fields', this.scope)
                            .localeCompare(this.translate(v2, 'fields', this.scope));
                    });

                    this.enabledFieldsList = [];
                    this.enabledFields = [];
                    this.disabledFields = [];

                    for (let i in layout) {
                        this.enabledFields.push({
                            name: layout[i],
                            label: this.getLanguage().translate(layout[i], 'fields', this.scope)
                        });

                        this.enabledFieldsList.push(layout[i]);
                    }

                    for (let i in allFields) {
                        if (!_.contains(this.enabledFieldsList, allFields[i])) {
                            this.disabledFields.push({
                                name: allFields[i],
                                label: this.getLanguage().translate(allFields[i], 'fields', this.scope)
                            });
                        }
                    }

                    this.rowLayout = this.enabledFields;

                    for (let i in this.rowLayout) {
                        this.rowLayout[i].label = this.getLanguage().translate(this.rowLayout[i].name, 'fields', this.scope);
                    }

                    callback();
                });
            });
        },

        fetch: function () {
            var layout = [];

            $("#layout ul.enabled > li").each((i, el) => {
                layout.push($(el).data('name'));
            });

            return layout;
        },

        checkFieldType: function (type) {
            return this.getFieldManager().checkFilter(type);
        },

        validate: function () {
            return true;
        },

        isFieldEnabled: function (model, name) {
            if (this.ignoreList.indexOf(name) !== -1) {
                return false;
            }

            return !model.getFieldParam(name, 'disabled') &&
                !model.getFieldParam(name, 'utility') &&
                !model.getFieldParam(name, 'layoutFiltersDisabled');
        },
    });
});
PK]\�eNJJ-views/admin/layouts/record/edit-attributes.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/layouts/record/edit-attributes', ['views/record/base'], function (Dep) {

    return Dep.extend({

        template: 'admin/layouts/record/edit-attributes',

        /** @internal Important for dynamic logic working. */
        mode: 'edit',

        data: function () {
            return {
                attributeDataList: this.getAttributeDataList()
            };
        },

        getAttributeDataList: function () {
            var list = [];

            this.attributeList.forEach(item => {
                let type = (this.attributeDefs[item] || {}).type;

                let isWide = !['enum', 'bool', 'int', 'float', 'varchar'].includes(type);

                list.push({
                    name: item,
                    viewKey: item + 'Field',
                    isWide: isWide,
                });
            });

            return list;
        },

        setup: function () {
            Dep.prototype.setup.call(this);

            this.attributeList = this.options.attributeList || [];
            this.attributeDefs = this.options.attributeDefs || {};

            this.attributeList.forEach(field => {
                var params = this.attributeDefs[field] || {};
                var type = params.type || 'base';

                var viewName = params.view || this.getFieldManager().getViewName(type);

                this.createField(field, viewName, params);
            });
        },
    });
});
PK]�<�v
v
.views/admin/layouts/modals/panel-attributes.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/layouts/modals/panel-attributes', ['views/modal', 'model'], function (Dep, Model) {

    return Dep.extend({

        templateContent: `
            <div class="panel panel-default no-side-margin">
                <div class="panel-body">
                    <div class="edit-container">{{{edit}}}</div>
                </div>
            </div>
        `,

        className: 'dialog dialog-record',

        shortcutKeys: {
            'Control+Enter': function (e) {
                this.actionSave();

                e.preventDefault();
                e.stopPropagation();
            },
        },

        setup: function () {
            this.buttonList = [
                {
                    name: 'save',
                    text: this.translate('Apply'),
                    style: 'primary',
                },
                {
                    name: 'cancel',
                    text: 'Cancel',
                },
            ];

            let model = new Model();

            model.name = 'LayoutManager';
            model.set(this.options.attributes || {});

            let attributeList = this.options.attributeList;
            let attributeDefs = this.options.attributeDefs;

            this.createView('edit', 'views/admin/layouts/record/edit-attributes', {
                selector: '.edit-container',
                attributeList: attributeList,
                attributeDefs: attributeDefs,
                model: model,
                dynamicLogicDefs: this.options.dynamicLogicDefs,
            });
        },

        actionSave: function () {
            let editView = this.getView('edit');
            let attrs = editView.fetch();

            editView.model.set(attrs, {silent: true});

            if (editView.validate()) {
                return;
            }

            let attributes = editView.model.attributes;

            this.trigger('after:save', attributes);

            return true;
        },
    });
});
PK]�t�i$views/admin/layouts/modals/create.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/admin/layouts/modals/create */

import ModalView from 'views/modal';
import EditForModalRecordView from 'views/record/edit-for-modal';
import Model from 'model';
import EnumFieldView from 'views/fields/enum';
import VarcharFieldView from 'views/fields/varchar';

class LayoutCreateModalView extends ModalView {

    // language=Handlebars
    templateContent = `
        <div class="complex-text-container">{{complexText info}}</div>
        <div class="record no-side-margin">{{{record}}}</div>
    `

    className = 'dialog dialog-record'

    /**
     * @typedef {Object} module:views/admin/layouts/modals/create~data
     * @property {string} type
     * @property {string} name
     * @property {string} label
     */

    /**
     * @param {{scope: string}} options
     */
    constructor(options) {
        super();

        this.scope = options.scope;
    }

    data() {
        return {
            info: this.translate('createInfo', 'messages', 'LayoutManager'),
        }
    }

    setup() {
        this.headerText = this.translate('Create');

        this.buttonList = [
            {
                name: 'create',
                style: 'danger',
                label: 'Create',
                onClick: () => this.actionCreate(),
            },
            {
                name: 'cancel',
                label: 'Cancel',
            },
        ];

        this.model = new Model({
            type: 'list',
            name: 'listForMyEntityType',
            label: 'List (for MyEntityType)',
        });

        this.recordView = new EditForModalRecordView({
            model: this.model,
            detailLayout: [
                {
                    columns: [
                        [
                            {
                                view: new EnumFieldView({
                                    name: 'type',
                                    params: {
                                        readOnly: true,
                                        translation: 'Admin.layouts',
                                        options: ['list'],
                                    },
                                    labelText: this.translate('type', 'fields', 'Admin'),
                                }),
                            },
                            {
                                view: new VarcharFieldView({
                                    name: 'name',
                                    params: {
                                        required: true,
                                        noSpellCheck: true,
                                        pattern: '$latinLetters',
                                    },
                                    labelText: this.translate('name', 'fields'),
                                }),
                            },
                            {
                                view: new VarcharFieldView({
                                    name: 'label',
                                    params: {
                                        required: true,
                                        pattern: '$noBadCharacters',
                                    },
                                    labelText: this.translate('label', 'fields', 'Admin'),
                                }),
                            },
                        ],
                        []
                    ]
                }
            ]
        });

        this.assignView('record', this.recordView, '.record');
    }

    actionCreate() {
        this.recordView.fetch();

        if (this.recordView.validate()) {
            return;
        }

        this.disableButton('create');

        Espo.Ui.notify(' ... ');

        Espo.Ajax
            .postRequest('Layout/action/create', {
                scope: this.scope,
                type: this.model.get('type'),
                name: this.model.get('name'),
                label: this.model.get('label'),
            })
            .then(() => {
                this.reRender();

                Espo.Ui.success('Created', {suppress: true});

                this.trigger('done');

                this.close();
            })
            .catch(() => {
                this.enableButton('create');
            });
    }
}

export default LayoutCreateModalView;
PK]䏩��-views/admin/layouts/modals/edit-attributes.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/layouts/modals/edit-attributes', ['views/modal', 'model'], function (Dep, Model) {

    return Dep.extend({

        templateContent: `
            <div class="panel panel-default no-side-margin">
                <div class="panel-body">
                    <div class="edit-container">{{{edit}}}</div>
                </div>
            </div>
        `,

        className: 'dialog dialog-record',

        shortcutKeys: {
            'Control+Enter': function (e) {
                this.actionSave();

                e.preventDefault();
                e.stopPropagation();
            },
        },

        setup: function () {
            this.buttonList = [
                {
                    name: 'save',
                    text: this.translate('Apply'),
                    style: 'primary',
                },
                {
                    name: 'cancel',
                    text: this.translate('Cancel'),
                },
            ];

            let model = new Model();

            model.name = 'LayoutManager';

            model.set(this.options.attributes || {});

            this.headerText = null;

            if (this.options.languageCategory) {
                this.headerText = this.translate(
                    this.options.name,
                    this.options.languageCategory,
                    this.options.scope
                );
            }

            let attributeList = Espo.Utils.clone(this.options.attributeList || []);

            let filteredAttributeList = [];

            attributeList.forEach(item => {
                if ((this.options.attributeDefs[item] || {}).readOnly) {
                    return;
                }

                filteredAttributeList.push(item);
            });

            attributeList = filteredAttributeList;

            this.createView('edit', 'views/admin/layouts/record/edit-attributes', {
                selector: '.edit-container',
                attributeList: attributeList,
                attributeDefs: this.options.attributeDefs,
                dynamicLogicDefs: this.options.dynamicLogicDefs,
                model: model,
            });
        },

        actionSave: function () {
            let editView = this.getView('edit');

            let attrs = editView.fetch();

            editView.model.set(attrs, {silent: true});

            if (editView.validate()) {
                return;
            }

            let attributes = editView.model.attributes;

            this.trigger('after:save', attributes);

            return true;
        },
    });
});
PK]u�����%views/admin/layouts/detail-convert.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/layouts/detail-convert',[ 'views/admin/layouts/detail'], function (Dep) {

    return Dep.extend({});
});
PK]�&�I�Iviews/admin/layouts/grid.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/layouts/grid', ['views/admin/layouts/base'], function (Dep) {

    return Dep.extend({

        template: 'admin/layouts/grid',

        dataAttributeList: null,
        panels: null,
        columnCount: 2,
        panelDataAttributeList: ['panelName', 'style'],
        panelDataAttributesDefs: {},
        panelDynamicLogicDefs: null,

        data: function () {
            return {
                scope: this.scope,
                type: this.type,
                buttonList: this.buttonList,
                enabledFields: this.enabledFields,
                disabledFields: this.disabledFields,
                panels: this.panels,
                columnCount: this.columnCount,
                panelDataList: this.getPanelDataList(),
            };
        },

        emptyCellTemplate:
            '<li class="empty disabled cell">' +
            '<a role="button" data-action="minusCell" class="remove-field"><i class="fas fa-minus"></i></a>' +
            '</li>',

        additionalEvents: {
            'click #layout a[data-action="addPanel"]': function () {
                this.addPanel();
                this.setIsChanged();
                this.makeDraggable();
            },
            'click #layout a[data-action="removePanel"]': function (e) {
                $(e.target).closest('ul.panels > li').find('ul.cells > li').each((i, li) => {
                    if ($(li).attr('data-name')) {
                        $(li).appendTo($('#layout ul.disabled'));
                    }
                });

                $(e.target).closest('ul.panels > li').remove();

                var number = $(e.currentTarget).data('number');
                this.clearView('panels-' + number);

                var index = -1;

                this.panels.forEach((item, i) => {
                    if (item.number === number) {
                        index = i;
                    }
                });

                if (~index) {
                    this.panels.splice(index, 1);
                }

                this.normalizeDisabledItemList();

                this.setIsChanged();
            },
            'click #layout a[data-action="addRow"]': function (e) {
                var tpl = this.unescape($("#layout-row-tpl").html());
                var html = _.template(tpl);

                $(e.target).closest('ul.panels > li').find('ul.rows').append(html);

                this.setIsChanged();
                this.makeDraggable();
            },
            'click #layout a[data-action="removeRow"]': function (e) {
                $(e.target).closest('ul.rows > li').find('ul.cells > li').each((i, li) => {
                    if ($(li).attr('data-name')) {
                        $(li).appendTo($('#layout ul.disabled'));
                    }
                });

                $(e.target).closest('ul.rows > li').remove();

                this.normalizeDisabledItemList();

                this.setIsChanged();
            },
            'click #layout a[data-action="removeField"]': function (e) {
                var $li = $(e.target).closest('li');
                var index = $li.index();
                var $ul = $li.parent();

                $li.appendTo($('ul.disabled'));

                var $empty = $($('#empty-cell-tpl').html());

                if (parseInt($ul.attr('data-cell-count')) === 1) {
                    for (var i = 0; i < this.columnCount; i++) {
                        $ul.append($empty.clone());
                    }
                } else {
                    if (index == 0) {
                        $ul.prepend($empty);
                    } else {
                        $empty.insertAfter($ul.children(':nth-child(' + index + ')'));
                    }
                }

                var cellCount = $ul.children().length;
                $ul.attr('data-cell-count', cellCount.toString());
                $ul.closest('li').attr('data-cell-count', cellCount.toString());

                this.setIsChanged();

                this.makeDraggable();
            },
            'click #layout a[data-action="minusCell"]': function (e) {
                if (this.columnCount < 2) {
                    return;
                }

                var $li = $(e.currentTarget).closest('li');
                var $ul = $li.parent();

                $li.remove();

                var cellCount = parseInt($ul.children().length || 2);

                this.setIsChanged();

                this.makeDraggable();

                $ul.attr('data-cell-count', cellCount.toString());
                $ul.closest('li').attr('data-cell-count', cellCount.toString());
            },
            'click #layout a[data-action="plusCell"]': function (e) {
                let $li = $(e.currentTarget).closest('li');
                let $ul = $li.find('ul');

                let $empty = $($('#empty-cell-tpl').html());

                $ul.append($empty);

                let cellCount = $ul.children().length;

                $ul.attr('data-cell-count', cellCount.toString());
                $ul.closest('li').attr('data-cell-count', cellCount.toString());

                this.setIsChanged();

                this.makeDraggable();
            },
            'click #layout a[data-action="edit-panel-label"]': function (e) {
                let $header = $(e.target).closest('header');
                let $label = $header.children('label');
                let panelName = $label.text();

                let id = $header.closest('li').data('number').toString();

                let attributes = {
                    panelName: panelName,
                };

                this.panelDataAttributeList.forEach((item) => {
                    if (item === 'panelName') {
                        return;
                    }

                    attributes[item] = this.panelsData[id][item];
                });

                var attributeList = this.panelDataAttributeList;
                var attributeDefs = this.panelDataAttributesDefs;

                this.createView('dialog', 'views/admin/layouts/modals/panel-attributes', {
                    attributeList: attributeList,
                    attributeDefs: attributeDefs,
                    attributes: attributes,
                    dynamicLogicDefs: this.panelDynamicLogicDefs,
                }, (view) => {
                    view.render();

                    this.listenTo(view, 'after:save', (attributes) => {
                        $label.text(attributes.panelName);
                        $label.attr('data-is-custom', 'true');

                        this.panelDataAttributeList.forEach((item) => {
                            if (item === 'panelName') {
                                return;
                            }

                            this.panelsData[id][item] = attributes[item];
                        });

                        view.close();

                        this.$el.find('.well').focus();

                        this.setIsChanged();
                    });
                });
            }
        },

        normalizeDisabledItemList: function () {
            //$('#layout ul.cells.disabled > li').each((i, el) => {});
        },

        setup: function () {
            Dep.prototype.setup.call(this);

            this.events = {
                ...this.additionalEvents,
                ...this.events,
            };

            this.panelsData = {};

            Espo.loader.require('res!client/css/misc/layout-manager-grid.css', styleCss => {
                this.$style = $('<style>').html(styleCss).appendTo($('body'));
            });
        },

        onRemove: function () {
            if (this.$style) this.$style.remove();
        },

        addPanel: function () {
            this.lastPanelNumber ++;

            let number = this.lastPanelNumber;

            let data = {
                customLabel: null,
                rows: [[]],
                number: number,
            };

            this.panels.push(data);

            let attributes = {};

            for (let attribute in this.panelDataAttributesDefs) {
                let item = this.panelDataAttributesDefs[attribute];

                if ('default' in item) {
                    attributes[attribute] = item.default;
                }
            }

            this.panelsData[number.toString()] = attributes;

            var $li = $('<li class="panel-layout"></li>');

            $li.attr('data-number', number);

            this.$el.find('ul.panels').append($li);

            this.createPanelView(data, true, (view) => {
                view.render();
            });
        },

        getPanelDataList: function () {
            var panelDataList = [];

            this.panels.forEach((item) => {
                var o = {};

                o.viewKey = 'panel-' + item.number;
                o.number = item.number;

                panelDataList.push(o);
            });

            return panelDataList;
        },

        prepareLayout: function () {
            return new Promise(resolve => {
                let countLoaded = 0;

                this.setupPanels(() => {
                    countLoaded ++;

                    if (countLoaded === this.panels.length) {
                        resolve();
                    }
                });
            });
        },

        setupPanels: function (callback) {
            this.lastPanelNumber = -1;

            this.panels = Espo.Utils.cloneDeep(this.panels);

            this.panels.forEach((panel, i) => {
                panel.number = i;
                this.lastPanelNumber ++;
                this.createPanelView(panel, false, callback);
                this.panelsData[i.toString()] = panel;
            });
        },

        createPanelView: function (data, empty, callback) {
            data.label = data.label || '';

            data.isCustomLabel = false;

            if (data.customLabel) {
                data.labelTranslated = data.customLabel;
                data.isCustomLabel = true;
            } else {
                data.labelTranslated = this.translate(data.label, 'labels', this.scope);
            }

            data.style = data.style || null;

            data.rows.forEach((row) => {
                let rest = this.columnCount - row.length;

                if (empty) {
                    for (let i = 0; i < rest; i++) {
                        row.push(false);
                    }
                }

                for (let i in row) {
                    if (row[i] !== false) {
                        row[i].label = this.getLanguage().translate(row[i].name, 'fields', this.scope);

                        if ('customLabel' in row[i]) {
                            row[i].hasCustomLabel = true;
                        }
                    }
                }
            });

            this.createView('panel-' + data.number, 'view', {
                selector: 'li.panel-layout[data-number="'+data.number+'"]',
                template: 'admin/layouts/grid-panel',
                data: () => {
                    var o = Espo.Utils.clone(data);

                    o.dataAttributeList = [];

                    this.panelDataAttributeList.forEach((item) => {
                        if (item === 'panelName') {
                            return;
                        }

                        o.dataAttributeList.push(item);
                    });

                    return o;
                }
            }, callback);
        },

        makeDraggable: function () {
            var self = this;

            $('#layout ul.panels').sortable({
                distance: 4,
                update: () => {
                    this.setIsChanged();
                },
            });

            $('#layout ul.panels').disableSelection();

            $('#layout ul.rows').sortable({
                distance: 4,
                connectWith: '.rows',
                update: () => {
                    this.setIsChanged();
                },
            });
            $('#layout ul.rows').disableSelection();

            $('#layout ul.cells > li')
                .draggable({revert: 'invalid', revertDuration: 200, zIndex: 10})
                .css('cursor', 'pointer');

            $('#layout ul.cells > li').droppable().droppable('destroy');

            $('#layout ul.cells:not(.disabled) > li').droppable({
                accept: '.cell',
                zIndex: 10,
                hoverClass: 'ui-state-hover',
                drop: function (e, ui) {
                    var index = ui.draggable.index();
                    var parent = ui.draggable.parent();

                    if (parent.get(0) == $(this).parent().get(0)) {
                        if ($(this).index() < ui.draggable.index()) {
                            $(this).before(ui.draggable);
                        } else {
                            $(this).after(ui.draggable);
                        }
                    } else {
                        ui.draggable.insertAfter($(this));

                        if (index == 0) {
                            $(this).prependTo(parent);
                        } else {
                            $(this).insertAfter(parent.children(':nth-child(' + (index) + ')'));
                        }
                    }

                    var $target = $(this);
                    var $draggable = $(ui.draggable);

                    ui.draggable.css({
                        top: 0,
                        left: 0,
                    });

                    if ($(this).parent().hasClass('disabled') && !$(this).data('name')) {
                        $(this).remove();
                    }

                    self.makeDraggable();

                    self.setIsChanged();
                }
            });
        },

        afterRender: function () {
            this.makeDraggable();

            let wellElement = /** @type {HTMLElement} */this.$el.find('.enabled-well').get(0)

            wellElement.focus({preventScroll: true});
        },

        fetch: function () {
            var layout = [];

            $("#layout ul.panels > li").each((i, el) => {
                var $label = $(el).find('header label');

                var id = $(el).data('number').toString();

                var o = {
                    rows: []
                };

                this.panelDataAttributeList.forEach((item) => {
                    if (item === 'panelName') {
                        return;
                    }

                    o[item] = this.panelsData[id][item];
                });

                o.style = o.style || 'default';

                var name = $(el).find('header').data('name');

                if (name) {
                    o.name = name;
                }

                if ($label.attr('data-is-custom')) {
                    o.customLabel = $label.text();
                } else {
                    o.label = $label.data('label');
                }

                $(el).find('ul.rows > li').each((i, li) => {
                    var row = [];

                    $(li).find('ul.cells > li').each((i, li) => {
                        var cell = false;

                        if (!$(li).hasClass('empty')) {
                            cell = {};

                            this.dataAttributeList.forEach((attr) => {
                                if (attr === 'customLabel') {
                                    if ($(li).get(0).hasAttribute('data-custom-label')) {
                                        cell[attr] = $(li).attr('data-custom-label');
                                    }

                                    return;
                                }

                                var value = $(li).data(Espo.Utils.toDom(attr)) || null;

                                if (value) {
                                    cell[attr] = value;
                                }
                            });
                        }

                        row.push(cell);
                    });

                    o.rows.push(row);
                });

                layout.push(o);
            });

            return layout;
        },

        validate: function (layout) {
            let fieldCount = 0;

            layout.forEach(panel => {
                panel.rows.forEach(row => {
                    row.forEach(cell => {
                        if (cell !== false && cell !== null) {
                            fieldCount++;
                        }
                    });
                });
            });

            if (fieldCount === 0) {
                Espo.Ui.error(
                    this.translate('cantBeEmpty', 'messages', 'LayoutManager')
                );

                return false;
            }

            return true;
        },
    });
});
PK]�:n!!views/admin/layouts/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/layouts/list', ['views/admin/layouts/rows'], function (Dep) {

    return Dep.extend({

        dataAttributeList: [
            'name',
            'width',
            'widthPx',
            'link',
            'notSortable',
            'noLabel',
            'align',
            'view',
            'customLabel',
            'label',
        ],

        dataAttributesDefs: {
            link: {
                type: 'bool',
                tooltip: true,
            },
            width: {
                type: 'float',
                min: 0,
                max: 100,
                tooltip: true,
            },
            widthPx: {
                type: 'int',
                min: 0,
                max: 720,
                tooltip: true,
            },
            notSortable: {
                type: 'bool',
                tooltip: true,
            },
            align: {
                type: 'enum',
                options: ['left', 'right'],
            },
            view: {
                type: 'varchar',
                readOnly: true,
            },
            noLabel: {
                type: 'bool',
                tooltip: true,
            },
            customLabel: {
                type: 'varchar',
                readOnly: true,
            },
            name: {
                type: 'varchar',
                readOnly: true,
            },
            label: {
                type: 'varchar',
                readOnly: true,
            },
        },

        dataAttributesDynamicLogicDefs: {
            fields: {
                widthPx: {
                    visible: {
                        conditionGroup: [
                            {
                                attribute: 'width',
                                type: 'isEmpty',
                            }
                        ]
                    }
                },
            }
        },

        editable: true,

        languageCategory: 'fields',

        ignoreList: [],

        ignoreTypeList: [],

        setup: function () {
            Dep.prototype.setup.call(this);

            this.wait(true);

            this.loadLayout(() => {
                this.wait(false);
            });
        },

        loadLayout: function (callback) {
            this.getModelFactory().create(Espo.Utils.hyphenToUpperCamelCase(this.scope), (model) => {
                this.getHelper().layoutManager.getOriginal(this.scope, this.type, this.setId, (layout) => {
                    this.readDataFromLayout(model, layout);

                    if (callback) {
                        callback();
                    }
                });
            });
        },

        readDataFromLayout: function (model, layout) {
            var allFields = [];

            for (let field in model.defs.fields) {
                if (this.checkFieldType(model.getFieldParam(field, 'type')) && this.isFieldEnabled(model, field)) {

                    allFields.push(field);
                }
            }

            allFields.sort((v1, v2) => {
                return this.translate(v1, 'fields', this.scope)
                    .localeCompare(this.translate(v2, 'fields', this.scope));
            });

            this.enabledFieldsList = [];

            this.enabledFields = [];
            this.disabledFields = [];

            var labelList = [];
            var duplicateLabelList = [];

            for (let i in layout) {
                let label = this.getLanguage().translate(layout[i].name, 'fields', this.scope);

                if (~labelList.indexOf(label)) {
                    duplicateLabelList.push(label);
                }

                labelList.push(label);

                this.enabledFields.push({
                    name: layout[i].name,
                    label: label,
                });

                this.enabledFieldsList.push(layout[i].name);
            }

            for (let i in allFields) {
                if (!_.contains(this.enabledFieldsList, allFields[i])) {
                    let label = this.getLanguage().translate(allFields[i], 'fields', this.scope);

                    if (~labelList.indexOf(label)) {

                        duplicateLabelList.push(label);
                    }

                    labelList.push(label);

                    let fieldName = allFields[i];

                    let o = {
                        name: fieldName,
                        label: label,
                    };

                    let fieldType = this.getMetadata().get(['entityDefs', this.scope, 'fields', fieldName, 'type']);

                    if (fieldType) {
                        if (this.getMetadata().get(['fields', fieldType, 'notSortable'])) {
                            o.notSortable = true;

                            this.itemsData[fieldName] = this.itemsData[fieldName] || {};
                            this.itemsData[fieldName].notSortable = true;
                        }
                    }

                    this.disabledFields.push(o);
                }
            }

            this.enabledFields.forEach(item => {
                if (~duplicateLabelList.indexOf(item.label)) {
                    item.label += ' (' + item.name + ')';
                }
            });

            this.disabledFields.forEach(item => {
                if (~duplicateLabelList.indexOf(item.label)) {
                    item.label += ' (' + item.name + ')';
                }
            });

            this.rowLayout = layout;

            for (let i in this.rowLayout) {
                let label = this.getLanguage().translate(this.rowLayout[i].name, 'fields', this.scope);

                this.enabledFields.forEach(item => {
                    if (item.name === this.rowLayout[i].name) {
                        label = item.label;
                    }
                });

                this.rowLayout[i].label = label;
                this.itemsData[this.rowLayout[i].name] = Espo.Utils.cloneDeep(this.rowLayout[i]);
            }
        },

        checkFieldType: function (type) {
            return true;
        },

        isFieldEnabled: function (model, name) {
            if (this.ignoreList.indexOf(name) !== -1) {
                return false;
            }

            if (this.ignoreTypeList.indexOf(model.getFieldParam(name, 'type')) !== -1) {
                return false;
            }

            var layoutList = model.getFieldParam(name, 'layoutAvailabilityList');

            if (layoutList && !~layoutList.indexOf(this.type)) {
                return;
            }

            return !model.getFieldParam(name, 'disabled') &&
                !model.getFieldParam(name, 'utility') &&
                !model.getFieldParam(name, 'layoutListDisabled');
        },
    });
});
PK]�Dn5��)views/admin/layouts/default-side-panel.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/layouts/default-side-panel', ['views/admin/layouts/rows'], function (Dep) {

    return Dep.extend({

        dataAttributeList: ['name', 'view', 'customLabel'],

        dataAttributesDefs: {
            view: {
                type: 'varchar',
                readOnly: true
            },
            customLabel: {
                type: 'varchar',
                readOnly: true
            },
            name: {
                type: 'varchar',
                readOnly: true
            },
        },

        editable: false,

        languageCategory: 'fields',

        setup: function () {
            Dep.prototype.setup.call(this);

            this.wait(true);
            this.loadLayout(function () {
                this.wait(false);
            }.bind(this));
        },

        validate: function () {
            return true;
        },

        loadLayout: function (callback) {
            this.getModelFactory().create(Espo.Utils.hyphenToUpperCamelCase(this.scope), (model) => {
                this.getHelper().layoutManager.getOriginal(this.scope, this.type, this.setId, (layout) => {
                    this.readDataFromLayout(model, layout);

                    if (callback) {
                        callback();
                    }
                });
            });
        },

        readDataFromLayout: function (model, layout) {
            var allFields = [];

            for (let field in model.defs.fields) {
                if (
                    this.checkFieldType(model.getFieldParam(field, 'type')) &&
                    this.isFieldEnabled(model, field)
                ) {
                    allFields.push(field);
                }
            }

            allFields.sort((v1, v2) => {
                return this.translate(v1, 'fields', this.scope)
                    .localeCompare(this.translate(v2, 'fields', this.scope));
            });

            if (~allFields.indexOf('assignedUser')) {
                allFields.unshift(':assignedUser');
            }

            this.enabledFieldsList = [];

            this.enabledFields = [];
            this.disabledFields = [];

            var labelList = [];
            var duplicateLabelList = [];

            for (let i = 0; i < layout.length; i++) {
                let item = layout[i];

                if (typeof item !== 'object') {
                    item = {
                        name: item,
                    };
                }

                let realName = item.name;

                if (realName.indexOf(':') === 0)
                    realName = realName.substr(1);

                let label = this.getLanguage().translate(realName, 'fields', this.scope);

                if (realName !== item.name) {
                    label = label + ' *';
                }

                if (~labelList.indexOf(label)) {
                    duplicateLabelList.push(label);
                }

                labelList.push(label);

                this.enabledFields.push({
                    name: item.name,
                    label: label,
                });

                this.enabledFieldsList.push(item.name);
            }

            for (let i = 0; i < allFields.length; i++) {
                if (!_.contains(this.enabledFieldsList, allFields[i])) {
                    let label = this.getLanguage().translate(allFields[i], 'fields', this.scope);

                    if (~labelList.indexOf(label)) {
                        duplicateLabelList.push(label);
                    }

                    labelList.push(label);

                    let fieldName = allFields[i];
                    let realName = fieldName;

                    if (realName.indexOf(':') === 0)
                        realName = realName.substr(1);

                    label = this.getLanguage().translate(realName, 'fields', this.scope);

                    if (realName !== fieldName) {
                        label = label + ' *';
                    }

                    let o = {
                        name: fieldName,
                        label: label,
                    };

                    let fieldType = this.getMetadata().get(['entityDefs', this.scope, 'fields', fieldName, 'type']);

                    if (fieldType) {
                        if (this.getMetadata().get(['fields', fieldType, 'notSortable'])) {
                            o.notSortable = true;
                        }
                    }

                    this.disabledFields.push(o);
                }
            }

            this.enabledFields.forEach(item =>  {
                if (~duplicateLabelList.indexOf(item.label)) {
                    item.label += ' (' + item.name + ')';
                }
            });

            this.disabledFields.forEach(item => {
                if (~duplicateLabelList.indexOf(item.label)) {
                    item.label += ' (' + item.name + ')';
                }
            });

            this.rowLayout = layout;

            for (let i in this.rowLayout) {
                var label = this.getLanguage().translate(this.rowLayout[i].name, 'fields', this.scope);

                this.enabledFields.forEach(item => {
                    if (item.name === this.rowLayout[i].name) {
                        label = item.label;
                    }
                });

                this.rowLayout[i].label = label;

                this.itemsData[this.rowLayout[i].name] = Espo.Utils.cloneDeep(this.rowLayout[i]);
            }
        },

        checkFieldType: function (type) {
            return true;
        },

        isFieldEnabled: function (model, name) {
            if (~['modifiedAt', 'createdAt', 'modifiedBy', 'createdBy'].indexOf(name)) {
                return false;
            }

            let layoutList = model.getFieldParam(name, 'layoutAvailabilityList');

            if (layoutList && !~layoutList.indexOf(this.type)) {
                return false;
            }

            if (
                model.getFieldParam(name, 'disabled') ||
                model.getFieldParam(name, 'utility')
            ) {
                return false;
            }

            if (model.getFieldParam(name, 'layoutDefaultSidePanelDisabled')) {
                return false;
            }

            if (model.getFieldParam(name, 'layoutDetailDisabled')) {
                return false;
            }

            return true;
        },
    });
});
PK]P��:��views/admin/layouts/rows.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/layouts/rows', ['views/admin/layouts/base'], function (Dep) {

    return Dep.extend({

        template: 'admin/layouts/rows',

        dataAttributeList: null,
        dataAttributesDefs: {},
        editable: false,

        data: function () {
            return {
                scope: this.scope,
                type: this.type,
                buttonList: this.buttonList,
                enabledFields: this.enabledFields,
                disabledFields: this.disabledFields,
                layout: this.rowLayout,
                dataAttributeList: this.dataAttributeList,
                dataAttributesDefs: this.dataAttributesDefs,
                editable: this.editable,
            };
        },

        setup: function () {
            this.itemsData = {};

            Dep.prototype.setup.call(this);

            this.events['click a[data-action="editItem"]'] = e => {
                let name = $(e.target).closest('li').data('name');

                this.editRow(name);
            };

            this.on('update-item', (name, attributes) => {
                this.itemsData[name] = Espo.Utils.cloneDeep(attributes);
            });

            Espo.loader.require('res!client/css/misc/layout-manager-rows.css', styleCss => {
                this.$style = $('<style>').html(styleCss).appendTo($('body'));
            });
        },

        onRemove: function () {
            if (this.$style) this.$style.remove();
        },

        editRow: function (name) {
            var attributes = Espo.Utils.cloneDeep(this.itemsData[name] || {});
            attributes.name = name;

            this.openEditDialog(attributes)
        },

        afterRender: function () {
            $('#layout ul.enabled, #layout ul.disabled').sortable({
                connectWith: '#layout ul.connected',
                update: e => {
                    if (!$(e.target).hasClass('disabled')) {
                        this.onDrop(e);
                        this.setIsChanged();
                    }
                },
            });

            this.$el.find('.enabled-well').focus();
        },

        onDrop: function (e) {},

        fetch: function () {
            var layout = [];

            $("#layout ul.enabled > li").each((i, el) => {
                var o = {};

                var name = $(el).data('name');

                var attributes = this.itemsData[name] || {};
                attributes.name = name;

                this.dataAttributeList.forEach(attribute => {
                    var value = attributes[attribute] || null;

                    if (value) {
                        o[attribute] = value;
                    }
                });

                layout.push(o);
            });

            return layout;
        },

        validate: function (layout) {
            if (layout.length === 0) {
                this.notify('Layout cannot be empty', 'error');

                return false;
            }

            return true;
        }
    });
});
PK]#r��8�8views/admin/layouts/index.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';
import LayoutDefaultPageView from 'views/admin/layouts/default-page';
import LayoutCreateModalView from 'views/admin/layouts/modals/create';

class LayoutIndexView extends View {

    template = 'admin/layouts/index'

    scopeList = null
    baseUrl = '#Admin/layouts'
    typeList = [
        'list',
        'detail',
        'listSmall',
        'detailSmall',
        'bottomPanelsDetail',
        'filters',
        'massUpdate',
        'sidePanelsDetail',
        'sidePanelsEdit',
        'sidePanelsDetailSmall',
        'sidePanelsEditSmall',
    ]
    /**
     * @type {string|null}
     */
    scope = null
    /**
     * @type {string|null}
     */
    type = null

    data() {
        return {
            scopeList: this.scopeList,
            typeList: this.typeList,
            scope: this.scope,
            layoutScopeDataList: this.getLayoutScopeDataList(),
            headerHtml: this.getHeaderHtml(),
            em: this.em,
        };
    }

    setup() {
        this.addHandler('click', '#layouts-menu a.layout-link', 'onLayoutLinkClick');
        this.addHandler('click', 'a.accordion-toggle', 'onItemHeaderClick');
        this.addHandler('keydown.shortcuts', '', 'onKeyDown');

        this.addActionHandler('createLayout', () => this.actionCreateLayout());

        this.em = this.options.em || false;
        this.scope = this.options.scope || null;
        this.type = this.options.type || null;

        this.scopeList = [];

        let scopeFullList = this.getMetadata().getScopeList().sort((v1, v2) => {
            return this.translate(v1, 'scopeNamesPlural')
                .localeCompare(this.translate(v2, 'scopeNamesPlural'));
        });

        scopeFullList.forEach(scope => {
            if (
                this.getMetadata().get('scopes.' + scope + '.entity') &&
                this.getMetadata().get('scopes.' + scope + '.layouts')
            ) {
                this.scopeList.push(scope);
            }
        });

        if (this.em && this.scope) {
            if (this.scopeList.includes(this.scope)) {
                this.scopeList = [this.scope];
            }
            else {
                this.scopeList = [];
            }
        }

        this.on('after:render', () => {
            $("#layouts-menu a[data-scope='" + this.options.scope + "'][data-type='" + this.options.type + "']")
                .addClass('disabled');

            this.renderLayoutHeader();

            if (!this.options.scope || !this.options.type) {
                this.checkLayout();

                this.renderDefaultPage();
            }

            if (this.scope && this.options.type) {
                this.checkLayout();

                this.openLayout(this.options.scope, this.options.type);
            }
        });
    }

    checkLayout() {
        const scope = this.options.scope;
        const type = this.options.type;

        if (!scope) {
            return;
        }

        const item = this.getLayoutScopeDataList().find(item => item.scope === scope);

        if (!item) {
            throw new Espo.Exceptions.NotFound("Layouts not available for entity type.");
        }

        if (type && !item.typeList.includes(type)) {
            throw new Espo.Exceptions.NotFound("The layout type is not available for the entity type.");
        }
    }

    afterRender() {
        this.controlActiveButton();
    }

    controlActiveButton() {
        if (!this.scope) {
            return;
        }

        let $header = this.$el.find(`.accordion-toggle[data-scope="${this.scope}"]`);

        this.undisableLinks();

        if (this.em && this.scope && !this.type) {
            $header.addClass('disabled');

            return;
        }

        $header.removeClass('disabled');

        this.$el.find(`a.layout-link[data-scope="${this.scope}"][data-type="${this.type}"]`)
            .addClass('disabled');
    }

    /**
     * @param {MouseEvent} e
     */
    onLayoutLinkClick(e) {
        e.preventDefault();

        let scope = $(e.target).data('scope');
        let type = $(e.target).data('type');

        if (this.getContentView()) {
            if (this.scope === scope && this.type === type) {
                return;
            }
        }

        this.getRouter().checkConfirmLeaveOut(() => {
            this.openLayout(scope, type);

            this.controlActiveButton();
        });
    }

    openDefaultPage() {
        this.clearView('content');
        this.type = null;

        this.renderDefaultPage();
        this.controlActiveButton();

        this.navigate(this.scope);
    }

    /**
     * @param {MouseEvent} e
     */
    onItemHeaderClick(e) {
        e.preventDefault();

        if (this.em) {
            if (!this.getContentView()) {
                return;
            }

            this.getRouter().checkConfirmLeaveOut(() => {
                this.openDefaultPage();
            });

            return;
        }

        let $target = $(e.target);
        let scope = $target.data('scope');
        let $collapse = $('.collapse[data-scope="' + scope + '"]');

        $collapse.hasClass('in') ?
            $collapse.collapse('hide') :
            $collapse.collapse('show');
    }

    /**
     * @param {KeyboardEvent} e
     */
    onKeyDown(e) {
        let key = Espo.Utils.getKeyFromKeyEvent(e);

        if (!this.hasView('content')) {
            return;
        }

        if (key === 'Control+Enter' || key === 'Control+KeyS') {
            e.stopPropagation();
            e.preventDefault();

            this.getContentView().actionSave();
        }
    }

    undisableLinks() {
        $("#layouts-menu a.layout-link").removeClass('disabled');
    }

    /**
     * @return {module:views/admin/layouts/base}
     */
    getContentView() {
        return this.getView('content')
    }

    openLayout(scope, type) {
        this.scope = scope;
        this.type = type;

        this.navigate(scope, type);

        Espo.Ui.notify(' ... ');

        let typeReal = this.getMetadata()
            .get('clientDefs.' + scope + '.additionalLayouts.' + type + '.type') || type;

        this.createView('content', 'views/admin/layouts/' + Espo.Utils.camelCaseToHyphen(typeReal), {
            fullSelector: '#layout-content',
            scope: scope,
            type: type,
            setId: this.setId,
            em: this.em,
        }, view => {
            this.renderLayoutHeader();
            view.render();
            Espo.Ui.notify(false);

            $(window).scrollTop(0);

            if (this.em) {
                this.listenToOnce(view, 'cancel', () => {
                    this.openDefaultPage();
                });

                this.listenToOnce(view, 'after-delete', () => {
                    this.openDefaultPage();

                    Promise.all([
                        this.getMetadata().loadSkipCache(),
                        this.getLanguage().loadSkipCache(),
                    ]).then(() => {
                        this.reRender();
                    });
                });
            }
        });
    }

    navigate(scope, type) {
        let url = '#Admin/layouts/scope=' + scope;

        if (type) {
            url += '&type=' + type;
        }

        if (this.em) {
            url += '&em=true';
        }

        this.getRouter().navigate(url, {trigger: false});
    }

    renderDefaultPage() {
        $('#layout-header').html('').hide();

        if (this.em) {
            this.assignView('default', new LayoutDefaultPageView(), '#layout-content')
                .then(/** LayoutDefaultPageView */view => {
                    view.render();
                });

            return;
        }

        this.clearView('default');

        $('#layout-content').html(this.translate('selectLayout', 'messages', 'Admin'));
    }

    renderLayoutHeader() {
        let $header = $('#layout-header');

        if (!this.scope) {
            $header.html('');

            return;
        }

        let list = [];

        let separatorHtml = '<span class="breadcrumb-separator"><span class="chevron-right"></span></span>';

        if (!this.em) {
            list.push(
                $('<span>').text(this.translate(this.scope, 'scopeNames'))
            );
        }

        list.push(
            $('<span>').text(this.translateLayoutName(this.type, this.scope))
        );

        let html = list.map($item => $item.get(0).outerHTML).join(' ' + separatorHtml + ' ');

        $header.show().html(html);
    }

    updatePageTitle() {
        this.setPageTitle(this.getLanguage().translate('Layout Manager', 'labels', 'Admin'));
    }

    getHeaderHtml() {
        let separatorHtml = '<span class="breadcrumb-separator"><span class="chevron-right"></span></span>';

        let list = [];

        let $root = $('<a>')
            .attr('href', '#Admin')
            .text(this.translate('Administration'));

        list.push($root);

        if (this.em) {
            list.push(
                $('<a>')
                    .attr('href', '#Admin/entityManager')
                    .text(this.translate('Entity Manager', 'labels', 'Admin'))
            );

            if (this.scope) {
                list.push(
                    $('<a>')
                        .attr('href', `#Admin/entityManager/scope=` + this.scope)
                        .text(this.translate(this.scope, 'scopeNames'))
                );

                list.push(
                    $('<span>').text(this.translate('Layouts', 'labels', 'EntityManager'))
                );
            }
        } else {
            list.push(
                $('<span>').text(this.translate('Layout Manager', 'labels', 'Admin'))
            );
        }

        return list.map($item => $item.get(0).outerHTML).join(' ' + separatorHtml + ' ');
    }

    translateLayoutName(type, scope) {
        if (this.getLanguage().get(scope, 'layouts', type)) {
            return this.getLanguage().translate(type, 'layouts', scope);
        }

        return this.getLanguage().translate(type, 'layouts', 'Admin');
    }

    getLayoutScopeDataList() {
        const dataList = [];

        this.scopeList.forEach(scope => {
            const item = {};

            let typeList = Espo.Utils.clone(this.typeList);

            item.scope = scope;
            item.url = this.baseUrl + '/scope=' + scope;

            if (this.em) {
                item.url += '&em=true';
            }

            if (
                this.getMetadata().get(['clientDefs', scope, 'bottomPanels', 'edit'])
            ) {
                typeList.push('bottomPanelsEdit');
            }

            if (
                !this.getMetadata().get(['clientDefs', scope, 'defaultSidePanelDisabled']) &&
                !this.getMetadata().get(['clientDefs', scope, 'defaultSidePanelFieldList'])
            ) {
                typeList.push('defaultSidePanel');
            }

            if (this.getMetadata().get(['clientDefs', scope, 'kanbanViewMode'])) {
                typeList.push('kanban');
            }

            const additionalLayouts = this.getMetadata().get(['clientDefs', scope, 'additionalLayouts']) || {};

            for (const aItem in additionalLayouts) {
                typeList.push(aItem);
            }

            typeList = typeList.filter(name => {
                return !this.getMetadata()
                    .get(['clientDefs', scope, 'layout' + Espo.Utils.upperCaseFirst(name) + 'Disabled'])
            });

            const typeDataList = [];

            typeList.forEach(type => {
                let url = this.baseUrl + '/scope=' + scope + '&type=' + type;

                if (this.em) {
                    url += '&em=true';
                }

                typeDataList.push({
                    type: type,
                    url: url,
                    label: this.translateLayoutName(type, scope),
                });
            });

            item.typeList = typeList;
            item.typeDataList = typeDataList;

            dataList.push(item);
        });

        return dataList;
    }

    actionCreateLayout() {
        const view = new LayoutCreateModalView({scope: this.scope});

        this.assignView('dialog', view).then(/** LayoutCreateModalView */view => {
            view.render();

            this.listenToOnce(view, 'done', () => {
                Promise.all([
                    this.getMetadata().loadSkipCache(),
                    this.getLanguage().loadSkipCache(),
                ]).then(() => {
                    this.reRender();
                });
            });
        });
    }
}

export default LayoutIndexView;
PK]���,E
E
views/admin/jobs-settings.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/jobs-settings', ['views/settings/record/edit'], function (Dep) {

    return Dep.extend({

        layoutName: 'jobsSettings',

        saveAndContinueEditingAction: false,

        dynamicLogicDefs: {
            fields: {
                jobPoolConcurrencyNumber: {
                    visible: {
                        conditionGroup: [
                            {
                                type: 'isTrue',
                                attribute: 'jobRunInParallel'
                            }
                        ]
                    }
                }
            }
        },

        setup: function () {
            Dep.prototype.setup.call(this);

            if (this.getHelper().getAppParam('isRestrictedMode') && !this.getUser().isSuperAdmin()) {

                this.setFieldReadOnly('jobRunInParallel');
                this.setFieldReadOnly('jobMaxPortion');
                this.setFieldReadOnly('jobPoolConcurrencyNumber');
                this.setFieldReadOnly('daemonInterval');
                this.setFieldReadOnly('daemonMaxProcessNumber');
                this.setFieldReadOnly('daemonProcessTimeout');
            }
        },

    });
});
PK]y����'views/admin/integrations/google-maps.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/integrations/google-maps', ['views/admin/integrations/edit'], function (Dep) {

    return Dep.extend({
    });
});
PK]y��� views/admin/integrations/edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/integrations/edit', ['view', 'model'], function (Dep, Model) {

    return Dep.extend({

        template: 'admin/integrations/edit',

        data: function () {
            return {
                integration: this.integration,
                dataFieldList: this.dataFieldList,
                helpText: this.helpText
            };
        },

        events: {
            'click button[data-action="cancel"]': function () {
                this.getRouter().navigate('#Admin/integrations', {trigger: true});
            },
            'click button[data-action="save"]': function () {
                this.save();
            },
        },

        setup: function () {
            this.integration = this.options.integration;

            this.helpText = false;

            if (this.getLanguage().has(this.integration, 'help', 'Integration')) {
                this.helpText = this.translate(this.integration, 'help', 'Integration');
            }

            this.fieldList = [];

            this.dataFieldList = [];

            this.model = new Model();
            this.model.id = this.integration;
            this.model.name = 'Integration';
            this.model.urlRoot = 'Integration';

            this.model.defs = {
                fields: {
                    enabled: {
                        required: true,
                        type: 'bool',
                    },
                }
            };

            this.wait(true);

            this.fields = this.getMetadata().get('integrations.' + this.integration + '.fields');

            Object.keys(this.fields).forEach(name => {
                this.model.defs.fields[name] = this.fields[name];
                this.dataFieldList.push(name);
            });

            this.model.populateDefaults();

            this.listenToOnce(this.model, 'sync', () => {
                this.createFieldView('bool', 'enabled');

                Object.keys(this.fields).forEach(name => {
                    this.createFieldView(this.fields[name]['type'], name, null, this.fields[name]);
                });

                this.wait(false);
            });

            this.model.fetch();
        },

        hideField: function (name) {
            this.$el.find('label[data-name="'+name+'"]').addClass('hide');
            this.$el.find('div.field[data-name="'+name+'"]').addClass('hide');

            var view = this.getView(name);

            if (view) {
                view.disabled = true;
            }
        },

        showField: function (name) {
            this.$el.find('label[data-name="'+name+'"]').removeClass('hide');
            this.$el.find('div.field[data-name="'+name+'"]').removeClass('hide');

            var view = this.getView(name);

            if (view) {
                view.disabled = false;
            }
        },

        afterRender: function () {
            if (!this.model.get('enabled')) {
                this.dataFieldList.forEach(name => {
                    this.hideField(name);
                });
            }

            this.listenTo(this.model, 'change:enabled', () => {
                if (this.model.get('enabled')) {
                    this.dataFieldList.forEach(name => {
                        this.showField(name);
                    });
                } else {
                    this.dataFieldList.forEach(name => {
                        this.hideField(name);
                    });
                }
            });
        },

        createFieldView: function (type, name, readOnly, params) {
            var viewName = this.model.getFieldParam(name, 'view') || this.getFieldManager().getViewName(type);

            this.createView(name, viewName, {
                model: this.model,
                selector: '.field[data-name="' + name + '"]',
                defs: {
                    name: name,
                    params: params
                },
                mode: readOnly ? 'detail' : 'edit',
                readOnly: readOnly,
            });

            this.fieldList.push(name);
        },

        save: function () {
            this.fieldList.forEach(field => {
                var view = this.getView(field);

                if (!view.readOnly) {
                    view.fetchToModel();
                }
            });

            var notValid = false;

            this.fieldList.forEach(field => {
                var fieldView = this.getView(field);

                if (fieldView && !fieldView.disabled) {
                    notValid = fieldView.validate() || notValid;
                }
            });

            if (notValid) {
                this.notify('Not valid', 'error');

                return;
            }

            this.listenToOnce(this.model, 'sync', () => {
                this.notify('Saved', 'success');
            });

            Espo.Ui.notify(this.translate('saving', 'messages'));

            this.model.save();
        },
    });
});
PK]׏��!views/admin/integrations/index.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/integrations/index', ['view'], function (Dep) {

    return Dep.extend({

        template: 'admin/integrations/index',

        integrationList: null,
        integration: null,

        data: function () {
            return {
                integrationList: this.integrationList,
                integration: this.integration,
            };
        },

        events: {
            'click #integrations-menu a.integration-link': function (e) {
                let name = $(e.currentTarget).data('name');

                this.openIntegration(name);
            },
        },

        setup: function () {
            this.integrationList = Object
                .keys(this.getMetadata().get('integrations') || {})
                .sort((v1, v2) => this.translate(v1, 'titles', 'Integration')
                    .localeCompare(this.translate(v2, 'titles', 'Integration'))
                );

            this.integration = this.options.integration || null;

            this.on('after:render', () => {
                this.renderHeader();

                if (!this.integration) {
                    this.renderDefaultPage();
                } else {
                    this.openIntegration(this.integration);
                }
            });
        },

        openIntegration: function (integration) {
            this.integration = integration;

            this.getRouter().navigate('#Admin/integrations/name=' + integration, {trigger: false});

            var viewName = this.getMetadata().get('integrations.' + integration + '.view') ||
                'views/admin/integrations/' +
                Espo.Utils.camelCaseToHyphen(this.getMetadata().get('integrations.' + integration + '.authMethod'));

            Espo.Ui.notify(' ... ');

            this.createView('content', viewName, {
                fullSelector: '#integration-content',
                integration: integration,
            }, view => {
                this.renderHeader();

                view.render();

                Espo.Ui.notify(false);

                $(window).scrollTop(0);
            });
        },

        renderDefaultPage: function () {
            $('#integration-header').html('').hide();

            let msg;

            if (this.integrationList.length) {
                msg = this.translate('selectIntegration', 'messages', 'Integration');
            } else {
                msg = '<p class="lead">' + this.translate('noIntegrations', 'messages', 'Integration') + '</p>';
            }

            $('#integration-content').html(msg);
        },

        renderHeader: function () {
            if (!this.integration) {
                $('#integration-header').html('');

                return;
            }

            $('#integration-header').show().html(this.translate(this.integration, 'titles', 'Integration'));
        },

        updatePageTitle: function () {
            this.setPageTitle(this.getLanguage().translate('Integrations', 'labels', 'Admin'));
        },
    });
});
PK]���OO"views/admin/integrations/oauth2.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/integrations/oauth2', ['views/admin/integrations/edit'], function (Dep) {

    return Dep.extend({

        template: 'admin/integrations/oauth2',

        data: function () {
            let redirectUri = this.redirectUri ||
                (this.getConfig().get('siteUrl') + '?entryPoint=oauthCallback');

            return _.extend({
                redirectUri: redirectUri,
            }, Dep.prototype.data.call(this));
        },
    });
});
PK]d�I���views/admin/sms.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/sms', ['views/settings/record/edit'], function (Dep) {

    return Dep.extend({

        layoutName: 'sms',
    });
});
PK]G"��	�	views/admin/upgrade/done.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/upgrade/done', ['views/modal'], function (Dep) {

    return Dep.extend({

        cssName: 'done-modal',
        header: false,
        createButton: true,

        template: 'admin/upgrade/done',

        data: function () {
            return {
                version: this.options.version,
                text: this.translate('upgradeDone', 'messages', 'Admin').replace('{version}', this.options.version),
            };
        },

        setup: function () {
            this.on('remove', () => {
                window.location.reload();
            });

            this.buttonList = [
                {
                    name: 'close',
                    label: 'Close',
                    onClick: (dialog) => {
                        setTimeout(() => {
                            this.getRouter().navigate('#Admin', {trigger: true});
                        }, 500);

                        dialog.close();
                    },
                }
            ];

            this.header = this.getLanguage().translate('Upgraded successfully', 'labels', 'Admin');
        },
    });
});
PK]�:��

views/admin/upgrade/ready.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/upgrade/ready', ['views/modal'], function (Dep) {

    return Dep.extend({

        cssName: 'ready-modal',

        header: false,

        template: 'admin/upgrade/ready',

        createButton: true,

        data: function () {
            return {
                version: this.upgradeData.version,
                text: this.translate('upgradeVersion', 'messages', 'Admin')
                    .replace('{version}', this.upgradeData.version)
            };
        },

        setup: function () {
            this.buttonList = [
                {
                    name: 'run',
                    label: this.translate('Run Upgrade', 'labels', 'Admin'),
                    style: 'danger',
                },
                {
                    name: 'cancel',
                    label: 'Cancel',
                },
            ];

            this.upgradeData = this.options.upgradeData;

            this.header = this.getLanguage().translate('Ready for upgrade', 'labels', 'Admin');

        },

        actionRun: function () {
            this.trigger('run');
            this.remove();
        },
    });
});
PK]��$YYviews/admin/upgrade/index.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/upgrade/index', ['view'], function (Dep) {

    return Dep.extend({

        template: 'admin/upgrade/index',

        packageContents: null,

        data: function () {
            return {
                versionMsg: this.translate('Current version') + ': ' + this.getConfig().get('version'),
                infoMsg: this.translate('upgradeInfo', 'messages', 'Admin')
                    .replace('{url}', 'https://www.espocrm.com/documentation/administration/upgrading/'),
                backupsMsg: this.translate('upgradeBackup', 'messages', 'Admin'),
                upgradeRecommendation: this.translate('upgradeRecommendation', 'messages', 'Admin'),
                downloadMsg: this.translate('downloadUpgradePackage', 'messages', 'Admin')
                    .replace('{url}', 'https://www.espocrm.com/download/upgrades'),
            };
        },

        afterRender: function () {
            this.$el.find('.panel-body a').attr('target', '_BLANK');
        },

        events: {
            'change input[name="package"]': function (e) {
                this.$el.find('button[data-action="upload"]')
                    .addClass('disabled')
                    .attr('disabled', 'disabled');

                this.$el.find('.message-container').html('');

                var files = e.currentTarget.files;

                if (files.length) {
                    this.selectFile(files[0]);
                }
            },
            'click button[data-action="upload"]': function () {
                this.upload();
            },
        },

        setup: function () {
        },

        selectFile: function (file) {
            var fileReader = new FileReader();

            fileReader.onload = (e) => {
                this.packageContents = e.target.result;

                this.$el.find('button[data-action="upload"]')
                    .removeClass('disabled')
                    .removeAttr('disabled');
            };

            fileReader.readAsDataURL(file);
        },

        showError: function (msg) {
            msg = this.translate(msg, 'errors', 'Admin');

            this.$el.find('.message-container').html(msg);
        },

        upload: function () {
            this.$el.find('button[data-action="upload"]')
                .addClass('disabled')
                .attr('disabled', 'disabled');

            this.notify('Uploading...');

            Espo.Ajax
                .postRequest('Admin/action/uploadUpgradePackage', this.packageContents, {
                    contentType: 'application/zip',
                    timeout: 0,
                })
                .then(data => {
                    if (!data.id) {
                        this.showError(this.translate('Error occurred'));

                        return;
                    }

                    Espo.Ui.notify(false);

                    this.createView('popup', 'views/admin/upgrade/ready', {
                        upgradeData: data,
                    }, view => {
                        view.render();

                        this.$el.find('button[data-action="upload"]')
                            .removeClass('disabled')
                            .removeAttr('disabled');

                        view.once('run', () => {
                            view.close();

                            this.$el.find('.panel.upload').addClass('hidden');

                            this.run(data.id, data.version);
                        });
                    });
                })
                .catch(xhr => {
                    this.showError(xhr.getResponseHeader('X-Status-Reason'));

                    Espo.Ui.notify(false);
                });
        },

        textNotification: function (text) {
            this.$el.find('.notify-text').html(text);
        },

        run: function (id, version) {
            let msg = this.translate('Upgrading...', 'labels', 'Admin');

            Espo.Ui.notify(this.translate('pleaseWait', 'messages'));

            this.textNotification(msg);

            Espo.Ajax
                .postRequest('Admin/action/runUpgrade', {id: id}, {timeout: 0, bypassAppReload: true})
                .then(() => {
                    let cache = this.getCache();

                    if (cache) {
                        cache.clear();
                    }

                    this.createView('popup', 'views/admin/upgrade/done', {
                        version: version,
                    }, view => {
                        Espo.Ui.notify(false);

                        view.render();
                    });
                })
                .catch(xhr => {
                    this.$el.find('.panel.upload').removeClass('hidden');

                    let msg = xhr.getResponseHeader('X-Status-Reason');

                    this.textNotification(this.translate('Error') + ': ' + msg);
                });
        },
    });
});


PK])�����*views/admin/formula-sandbox/record/edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/formula-sandbox/record/edit', ['views/record/edit'], function (Dep) {

    return Dep.extend({

        scriptAreaHeight: 400,

        bottomView: null,

        sideView: null,

        buttonList: [
            {
                name: 'run',
                label: 'Run',
                style: 'danger',
                title: 'Ctrl+Enter',
            },
        ],

        dropdownItemList: [],

        isWide: true,

        accessControlDisabled: true,

        saveAndContinueEditingAction: false,

        saveAndNewAction: false,

        shortcutKeyCtrlEnterAction: 'run',

        setup: function () {
            this.scope = 'Formula';

            let additionalFunctionDataList = [
                {
                    "name": "output\\print",
                    "insertText": "output\\print(VALUE)"
                },
                {
                    "name": "output\\printLine",
                    "insertText": "output\\printLine(VALUE)"
                }
            ];

            this.detailLayout = [
                {
                    rows: [
                        [
                            false,
                            {
                                name: 'targetType',
                                labelText: this.translate('targetType', 'fields', 'Formula'),
                            },
                            {
                                name: 'target',
                                labelText: this.translate('target', 'fields', 'Formula'),
                            },
                        ]
                    ]
                },
                {
                    rows: [
                        [
                            {
                                name: 'script',
                                noLabel: true,
                                options: {
                                    targetEntityType: this.model.get('targetType'),
                                    height: this.scriptAreaHeight,
                                    additionalFunctionDataList: additionalFunctionDataList,
                                },
                            },
                        ]
                    ]
                },
                {
                    name: 'output',
                    rows: [
                        [
                            {
                                name: 'errorMessage',
                                labelText: this.translate('error', 'fields', 'Formula'),
                            },
                        ],
                        [
                            {
                                name: 'output',
                                labelText: this.translate('output', 'fields', 'Formula'),
                            },
                        ]
                    ]
                },
            ];

            Dep.prototype.setup.call(this);

            if (!this.model.get('targetType')) {
                this.hideField('target');
            }
            else {
                this.showField('target');
            }

            this.controlTargetTypeField();
            this.listenTo(this.model, 'change:targetId', () => this.controlTargetTypeField());

            this.controlOutputField();
            this.listenTo(this.model, 'change', () => this.controlOutputField());
        },

        controlTargetTypeField: function () {
            if (this.model.get('targetId')) {
                this.setFieldReadOnly('targetType');

                return;
            }

            this.setFieldNotReadOnly('targetType');
        },

        controlOutputField: function () {
            if (this.model.get('errorMessage')) {
                this.showField('errorMessage');
            }
            else {
                this.hideField('errorMessage');
            }
        },

        actionRun: function () {
            this.model.trigger('run');
        },
    });
});
PK]7A�$views/admin/formula-sandbox/index.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/formula-sandbox/index', ['view', 'model'], function (Dep, Model) {

    return Dep.extend({

        template: 'admin/formula-sandbox/index',

        targetEntityType: null,

        storageKey: 'formulaSandbox',

        data: function () {
            return {};
        },

        setup: function () {
            let entityTypeList = [''].concat(
                this.getMetadata()
                    .getScopeEntityList()
                    .filter(item => {
                        return this.getMetadata().get(['scopes', item, 'object']);
                    })
            );

            let data = {
                script: null,
                targetId: null,
                targetType: null,
                output: null,
            };

            if (this.getSessionStorage().has(this.storageKey)) {
                let storedData = this.getSessionStorage().get(this.storageKey);

                data.script = storedData.script || null;
                data.targetId = storedData.targetId || null;
                data.targetName = storedData.targetName || null;
                data.targetType = storedData.targetType || null;
            }

            let model = this.model = new Model();

            model.name = 'Formula';

            model.setDefs({
                fields: {
                    targetType: {
                        type: 'enum',
                        options: entityTypeList,
                        translation: 'Global.scopeNames',
                        view: 'views/fields/entity-type',
                    },
                    target: {
                        type: 'link',
                        entity: data.targetType,
                    },
                    script: {
                        type: 'formula',
                        view: 'views/fields/formula',
                    },
                    output: {
                        type: 'text',
                        readOnly: true,
                        displayRawText: true,
                        tooltip: true,
                    },
                    errorMessage: {
                        type: 'text',
                        readOnly: true,
                        displayRawText: true,
                    },
                }
            });

            model.set(data);

            this.createRecordView();

            this.listenTo(this.model, 'change:targetType', (m, v, o) => {
                if (!o.ui) {
                    return;
                }

                setTimeout(() => {
                    this.targetEntityType = this.model.get('targetType');

                    this.model.set({
                        targetId: null,
                        targetName: null,
                    }, {silent: true});

                    let attributes = Espo.Utils.cloneDeep(this.model.attributes);

                    this.clearView('record');

                    this.model.set(attributes, {silent: true});

                    this.model.defs.fields.target.entity = this.targetEntityType;

                    this.createRecordView()
                        .then(view => view.render());
                }, 10);
            });

            this.listenTo(this.model, 'run', () => this.run());

            this.listenTo(this.model, 'change', (m, o) => {
                if (!o.ui) {
                    return;
                }

                let dataToStore = {
                    script: this.model.get('script'),
                    targetType: this.model.get('targetType'),
                    targetId: this.model.get('targetId'),
                    targetName: this.model.get('targetName'),
                };

                this.getSessionStorage().set(this.storageKey, dataToStore);
            });
        },

        createRecordView: function () {
            return this.createView('record', 'views/admin/formula-sandbox/record/edit', {
                selector: '.record',
                model: this.model,
                targetEntityType: this.targetEntityType,
                confirmLeaveDisabled: true,
                shortcutKeysEnabled: true,
            });
        },

        updatePageTitle: function () {
            this.setPageTitle(this.getLanguage().translate('Formula Sandbox', 'labels', 'Admin'));
        },

        run: function () {
            let script = this.model.get('script');

            this.model.set({
                output: null,
                errorMessage: null,
            });

            if (script === '' || script === null) {
                this.model.set('output', null);

                Espo.Ui.warning(
                    this.translate('emptyScript', 'messages', 'Formula')
                );

                return;
            }

            Espo.Ajax
                .postRequest('Formula/action/run', {
                    expression: script,
                    targetId: this.model.get('targetId'),
                    targetType: this.model.get('targetType'),
                })
                .then(response => {
                    this.model.set('output', response.output || null);

                    console.log(this.model.get('script'));

                    let errorMessage = null;

                    if (!response.isSuccess) {
                        errorMessage = response.message || null;
                    }

                    this.model.set('errorMessage', errorMessage);

                    if (response.isSuccess) {
                        Espo.Ui.success(
                            this.translate('runSuccess', 'messages', 'Formula')
                        );

                        return;
                    }

                    if (response.isSyntaxError) {
                        let msg = this.translate('checkSyntaxError', 'messages', 'Formula');

                        if (response.message) {
                            msg += ' ' + response.message;
                        }

                        Espo.Ui.error(msg);

                        return;
                    }

                    let msg = this.translate('runError', 'messages', 'Formula');

                    if (response.message) {
                        msg += ' ' + response.message;
                    }

                    Espo.Ui.error(msg);
                });
        },
    });
});
PK]"+2̲�views/admin/outbound-emails.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/outbound-emails', ['views/settings/record/edit'], function (Dep) {

    return Dep.extend({

        layoutName: 'outboundEmails',

        saveAndContinueEditingAction: false,

        dynamicLogicDefs: {
            fields: {
                smtpUsername: {
                    visible: {
                        conditionGroup: [
                            {
                                type: 'isNotEmpty',
                                attribute: 'smtpServer',
                            },
                            {
                                type: 'isTrue',
                                attribute: 'smtpAuth',
                            }
                        ]
                    },
                    required: {
                        conditionGroup: [
                            {
                                type: 'isNotEmpty',
                                attribute: 'smtpServer',
                            },
                            {
                                type: 'isTrue',
                                attribute: 'smtpAuth',
                            }
                        ]
                    }
                },
                smtpPassword: {
                    visible: {
                        conditionGroup: [
                            {
                                type: 'isNotEmpty',
                                attribute: 'smtpServer',
                            },
                            {
                                type: 'isTrue',
                                attribute: 'smtpAuth',
                            }
                        ]
                    }
                },
                smtpPort: {
                    visible: {
                        conditionGroup: [
                            {
                                type: 'isNotEmpty',
                                attribute: 'smtpServer',
                            },
                        ]
                    },
                    required: {
                        conditionGroup: [
                            {
                                type: 'isNotEmpty',
                                attribute: 'smtpServer',
                            },
                        ]
                    }
                },
                smtpSecurity: {
                    visible: {
                        conditionGroup: [
                            {
                                type: 'isNotEmpty',
                                attribute: 'smtpServer',
                            },
                        ]
                    }
                },
                smtpAuth: {
                    visible: {
                        conditionGroup: [
                            {
                                type: 'isNotEmpty',
                                attribute: 'smtpServer',
                            },
                        ]
                    }
                },
            },
        },

        setup: function () {
            Dep.prototype.setup.call(this);
        },

        afterRender: function () {
            Dep.prototype.afterRender.call(this);

            var smtpSecurityField = this.getFieldView('smtpSecurity');
            this.listenTo(smtpSecurityField, 'change', function () {
                var smtpSecurity = smtpSecurityField.fetch()['smtpSecurity'];
                if (smtpSecurity == 'SSL') {
                    this.model.set('smtpPort', '465');
                } else if (smtpSecurity == 'TLS') {
                    this.model.set('smtpPort', '587');
                } else {
                    this.model.set('smtpPort', '25');
                }
            }.bind(this));
        },

    });

});

PK]nT�[	[	8views/admin/dynamic-logic/conditions-string/group-not.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/dynamic-logic/conditions-string/group-not',
['views/admin/dynamic-logic/conditions-string/group-base'], function (Dep) {

    return Dep.extend({

        template: 'admin/dynamic-logic/conditions-string/group-not',

        data: function () {
            return {
                viewKey: this.viewKey,
                operator: this.operator
            };
        },

        setup: function () {
            this.level = this.options.level || 0;
            this.number = this.options.number || 0;
            this.scope = this.options.scope;

            this.operator = this.options.operator || this.operator;

            this.itemData = this.options.itemData || {};
            this.viewList = [];

            var i = 0;
            var key = 'view-' + this.level.toString() + '-' + this.number.toString() + '-' + i.toString();

            this.createItemView(i, key, this.itemData.value);
            this.viewKey = key;
        },
    });
});

PK]��i��>views/admin/dynamic-logic/conditions-string/item-value-enum.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/dynamic-logic/conditions-string/item-value-enum',
['views/admin/dynamic-logic/conditions-string/item-base'], function (Dep) {

    return Dep.extend({

        template: 'admin/dynamic-logic/conditions-string/item-base',

        createValueFieldView: function () {
            var key = this.getValueViewKey();

            var viewName = 'views/fields/enum';

            this.createView('value', viewName, {
                model: this.model,
                name: this.field,
                selector: '[data-view-key="'+key+'"]',
                params: {
                    options: this.getMetadata()
                    .get(['entityDefs', this.scope, 'fields', this.field, 'options']) || []
                }
            });
        },
    });
});
PK]�zߟ��Fviews/admin/dynamic-logic/conditions-string/item-operator-only-base.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/dynamic-logic/conditions-string/item-operator-only-base',
['views/admin/dynamic-logic/conditions-string/item-base'], function (Dep) {

    return Dep.extend({

        template: 'admin/dynamic-logic/conditions-string/item-operator-only-base',

        createValueFieldView: function () {},

    });
});
PK]��M�OO;views/admin/dynamic-logic/conditions-string/item-in-past.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/dynamic-logic/conditions-string/item-in-past',
['views/admin/dynamic-logic/conditions-string/item-operator-only-date'], function (Dep) {

    return Dep.extend({

        dateValue: 'past',
    });
});
PK]���d��>views/admin/dynamic-logic/conditions-string/item-value-link.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/dynamic-logic/conditions-string/item-value-link',
['views/admin/dynamic-logic/conditions-string/item-base'], function (Dep) {

    return Dep.extend({

        template: 'admin/dynamic-logic/conditions-string/item-base',

        createValueFieldView: function () {
            var key = this.getValueViewKey();

            var viewName = 'views/fields/link';

            this.createView('value', viewName, {
                model: this.model,
                name: 'link',
                selector: '[data-view-key="' + key + '"]',
                foreignScope: this.getMetadata().get(['entityDefs', this.scope, 'fields', this.field, 'entity']) ||
                    this.getMetadata().get(['entityDefs', this.scope, 'links', this.field, 'entity'])
            });
        },
    });
});
PK]��͆��9views/admin/dynamic-logic/conditions-string/group-base.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/dynamic-logic/conditions-string/group-base', ['view'], function (Dep) {

    return Dep.extend({

        template: 'admin/dynamic-logic/conditions-string/group-base',

        data: function () {
            if (!this.conditionList.length) {
                return {
                    isEmpty: true
                };
            }

            return {
                viewDataList: this.viewDataList,
                operator: this.operator,
                level: this.level
            };
        },

        setup: function () {
            this.level = this.options.level || 0;
            this.number = this.options.number || 0;
            this.scope = this.options.scope;


            this.operator = this.options.operator || this.operator;

            this.itemData = this.options.itemData || {};
            this.viewList = [];

            var conditionList = this.conditionList = this.itemData.value || [];

            this.viewDataList = [];

            conditionList.forEach(function (item, i) {
                var key = 'view-' + this.level.toString() + '-' + this.number.toString() + '-' + i.toString();

                this.createItemView(i, key, item);
                this.viewDataList.push({
                    key: key,
                    isEnd: i === conditionList.length - 1,
                });
            }, this);
        },

        getFieldType: function (item) {
            return this.getMetadata()
                .get(['entityDefs', this.scope, 'fields', item.attribute, 'type']) || 'base';
        },

        createItemView: function (number, key, item) {
            this.viewList.push(key);

            item = item || {};

            var additionalData = item.data || {};

            var type = additionalData.type || item.type || 'equals';

            var fieldType = this.getFieldType(item);

            var viewName = this.getMetadata()
                .get([
                    'clientDefs',
                    'DynamicLogic',
                    'fieldTypes',
                    fieldType,
                    'conditionTypes',
                    type,
                    'itemView'
                ]) ||
                this.getMetadata().get(['clientDefs', 'DynamicLogic', 'itemTypes', type, 'view']);

            if (!viewName) {
                return;
            }

            var operator = this.getMetadata()
                .get(['clientDefs', 'DynamicLogic', 'itemTypes', type, 'operator']);

            var operatorString = this.getMetadata()
                    .get(['clientDefs', 'DynamicLogic', 'itemTypes', type, 'operatorString']);

            if (!operatorString) {
                operatorString = this.getLanguage()
                    .translateOption(type, 'operators', 'DynamicLogic')
                    .toLowerCase();

                operatorString = '<i class="small">' + operatorString + '</i>';
            }

            this.createView(key, viewName, {
                itemData: item,
                scope: this.scope,
                level: this.level + 1,
                selector: '[data-view-key="'+key+'"]',
                number: number,
                operator: operator,
                operatorString: operatorString,
            });
        },
    });
});
PK]T%�SS<views/admin/dynamic-logic/conditions-string/item-is-today.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/dynamic-logic/conditions-string/item-is-today',
['views/admin/dynamic-logic/conditions-string/item-operator-only-date'], function (Dep) {

    return Dep.extend({

        dateValue: 'today',

    });
});
PK]��P�UU=views/admin/dynamic-logic/conditions-string/item-in-future.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/dynamic-logic/conditions-string/item-in-future',
['views/admin/dynamic-logic/conditions-string/item-operator-only-date'], function (Dep) {

    return Dep.extend({

        dateValue: 'future',
    });
});

PK]7�A��Hviews/admin/dynamic-logic/conditions-string/item-multiple-values-base.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/dynamic-logic/conditions-string/item-multiple-values-base',
['views/admin/dynamic-logic/conditions-string/item-base'], function (Dep) {

    return Dep.extend({

        template: 'admin/dynamic-logic/conditions-string/item-multiple-values-base',

        data: function () {
            return {
                valueViewDataList: this.valueViewDataList,
                scope: this.scope,
                operator: this.operator,
                operatorString: this.operatorString,
                field: this.field,
            };
        },

        populateValues: function () {
        },

        getValueViewKey: function (i) {
            return 'view-' + this.level.toString() + '-' + this.number.toString() + '-' + i.toString();
        },

        createValueFieldView: function () {
            var valueList = this.itemData.value || [];

            var fieldType = this.getMetadata().get(['entityDefs', this.scope, 'fields', this.field, 'type']) || 'base';
            var viewName = this.getMetadata().get(['entityDefs', this.scope, 'fields', this.field, 'view']) ||
                this.getFieldManager().getViewName(fieldType);

            this.valueViewDataList = [];

            valueList.forEach(function (value, i) {
                var model = this.model.clone();
                model.set(this.itemData.attribute, value);

                var key = this.getValueViewKey(i);

                this.valueViewDataList.push({
                    key: key,
                    isEnd: i === valueList.length - 1
                });

                this.createView(key, viewName, {
                    model: model,
                    name: this.field,
                    selector: '[data-view-key="'+key+'"]'
                });
            }, this);
        },
    });
});
PK]B\��8views/admin/dynamic-logic/conditions-string/item-base.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/dynamic-logic/conditions-string/item-base', ['view'], function (Dep) {

    return Dep.extend({

        template: 'admin/dynamic-logic/conditions-string/item-base',

        data: function () {
            return {
                valueViewKey: this.getValueViewKey(),
                scope: this.scope,
                operator: this.operator,
                operatorString: this.operatorString,
                field: this.field
            };
        },

        setup: function () {
            this.itemData = this.options.itemData;

            this.level = this.options.level || 0;
            this.number = this.options.number || 0;
            this.scope = this.options.scope;

            this.operator = this.options.operator || this.operator;
            this.operatorString = this.options.operatorString || this.operatorString;

            this.additionalData = (this.itemData.data || {});

            this.field = (this.itemData.data || {}).field || this.itemData.attribute;

            this.wait(true);

            this.getModelFactory().create(this.scope, function (model) {
                this.model = model;

                this.populateValues();

                this.createValueFieldView();

                this.wait(false);
            }, this);
        },

        populateValues: function () {
            if (this.itemData.attribute) {
                this.model.set(this.itemData.attribute, this.itemData.value);
            }
            this.model.set(this.additionalData.values || {});
        },

        getValueViewKey: function () {
            return 'view-' + this.level.toString() + '-' + this.number.toString() + '-0';
        },

        createValueFieldView: function () {
            var key = this.getValueViewKey();

            var fieldType = this.getMetadata().get(['entityDefs', this.scope, 'fields', this.field, 'type']) || 'base';
            var viewName = this.getMetadata().get(['entityDefs', this.scope, 'fields', this.field, 'view']) ||
                this.getFieldManager().getViewName(fieldType);

            this.createView('value', viewName, {
                model: this.model,
                name: this.field,
                selector: '[data-view-key="'+key+'"]'
            });
        },
    });
});

PK]A����Aviews/admin/dynamic-logic/conditions-string/item-value-varchar.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/dynamic-logic/conditions-string/item-value-varchar',
['views/admin/dynamic-logic/conditions-string/item-base'], function (Dep) {

    return Dep.extend({

        template: 'admin/dynamic-logic/conditions-string/item-base',

        createValueFieldView: function () {
            var key = this.getValueViewKey();

            var viewName = 'views/fields/varchar';

            this.createView('value', viewName, {
                model: this.model,
                name: this.field,
                selector: '[data-view-key="'+key+'"]',
            });
        },
    });
});
PK]��`5<<Fviews/admin/dynamic-logic/conditions-string/item-operator-only-date.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/dynamic-logic/conditions-string/item-operator-only-date',
['views/admin/dynamic-logic/conditions-string/item-operator-only-base'], function (Dep) {

    return Dep.extend({

        template: 'admin/dynamic-logic/conditions-string/item-operator-only-date',

        data: function () {
            var data = Dep.prototype.data.call(this);
            data.dateValue = this.dateValue;
            return data;
        },
    });
});
PK]���)views/admin/dynamic-logic/fields/field.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/dynamic-logic/fields/field', ['views/fields/multi-enum', 'ui/multi-select'],
function (Dep, /** module:ui/multi-select */MultiSelect) {

    return Dep.extend({

        getFieldList: function () {
            let fields = this.getMetadata().get('entityDefs.' + this.options.scope + '.fields');

            let filterList = Object.keys(fields).filter(field => {
                let fieldType = fields[field].type || null;

                if (
                    fields[field].disabled ||
                    fields[field].utility
                ) {
                    return;
                }

                if (!fieldType) {
                    return;
                }

                if (!this.getMetadata().get(['clientDefs', 'DynamicLogic', 'fieldTypes', fieldType])) {
                    return;
                }

                return true;
            });

            filterList.push('id');

            filterList.sort((v1, v2) => {
                return this.translate(v1, 'fields', this.options.scope)
                    .localeCompare(this.translate(v2, 'fields', this.options.scope));
            });

            return filterList;
        },

        setupTranslatedOptions: function () {
            this.translatedOptions = {};

            this.params.options.forEach(item => {
                this.translatedOptions[item] = this.translate(item, 'fields', this.options.scope);
            });
        },

        setupOptions: function () {
            Dep.prototype.setupOptions.call(this);

            this.params.options = this.getFieldList();
            this.setupTranslatedOptions();
        },

        afterRender: function () {
            Dep.prototype.afterRender.call(this);

            if (this.$element) {
                MultiSelect.focus(this.$element);
            }
        },
    });
});

PK]� �v�
�
(views/admin/dynamic-logic/modals/edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/dynamic-logic/modals/edit', ['views/modal'], function (Dep) {

    return Dep.extend({

        template: 'admin/dynamic-logic/modals/edit',

        className: 'dialog dialog-record',

        data: function () {
            return {
            };
        },

        events: {

        },

        buttonList: [
            {
                name: 'apply',
                label: 'Apply',
                style: 'primary'
            },
            {
                name: 'cancel',
                label: 'Cancel'
            }
        ],

        setup: function () {
            this.conditionGroup = Espo.Utils.cloneDeep(this.options.conditionGroup || []);
            this.scope = this.options.scope;

            this.createView('conditionGroup', 'views/admin/dynamic-logic/conditions/and', {
                selector: '.top-group-container',
                itemData: {
                    value: this.conditionGroup
                },
                scope: this.options.scope
            });
        },

        actionApply: function () {
            var data = this.getView('conditionGroup').fetch();

            var conditionGroup = data.value;

            this.trigger('apply', conditionGroup);
            this.close();
        },
    });
});


PK]h�0�J
J
-views/admin/dynamic-logic/modals/add-field.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/dynamic-logic/modals/add-field', ['views/modal', 'model'], function (Dep, Model) {

    return Dep.extend({

        templateContent: '<div class="field" data-name="field">{{{field}}}</div>',

        events: {
            'click a[data-action="addField"]': function (e) {
                this.trigger('add-field', $(e.currentTarget).data().name);
            }
        },

        setup: function () {
            this.header = this.translate('Add Field');
            this.scope = this.options.scope;

            var model = new Model();

            this.createView('field', 'views/admin/dynamic-logic/fields/field', {
                selector: '[data-name="field"]',
                model: model,
                mode: 'edit',
                scope: this.scope,
                defs: {
                    name: 'field',
                    params: {}
                }
            }, function (view) {
                this.listenTo(view, 'change', function () {
                    var list = model.get('field') || [];
                    if (!list.length) return;
                    this.trigger('add-field', list[0]);
                }, this);
            });
        },
    });
});
PK]��(((+views/admin/dynamic-logic/conditions/and.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/dynamic-logic/conditions/and', ['views/admin/dynamic-logic/conditions/group-base'], function (Dep) {

    return Dep.extend({

        operator: 'and',
    });
});
PK]s�D-4#4#2views/admin/dynamic-logic/conditions/group-base.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/dynamic-logic/conditions/group-base', ['view'], function (Dep) {

    return Dep.extend({

        template: 'admin/dynamic-logic/conditions/group-base',

        data: function () {
            return {
                viewDataList: this.viewDataList,
                operator: this.operator,
                level: this.level,
                groupOperator: this.getGroupOperator(),
            };
        },

        events: {
            'click > div.group-head > [data-action="remove"]': function (e) {
                e.stopPropagation();
                this.trigger('remove-item');
            },
            'click > div.group-bottom [data-action="addField"]': function (e) {
                this.actionAddField();
            },
            'click > div.group-bottom [data-action="addAnd"]': function (e) {
                this.actionAddGroup('and');
            },
            'click > div.group-bottom [data-action="addOr"]': function (e) {
                this.actionAddGroup('or');
            },
            'click > div.group-bottom [data-action="addNot"]': function (e) {
                this.actionAddGroup('not');
            }
        },

        setup: function () {
            this.level = this.options.level || 0;
            this.number = this.options.number || 0;
            this.scope = this.options.scope;

            this.itemData = this.options.itemData || {};
            this.viewList = [];

            var conditionList = this.conditionList = this.itemData.value || [];

            this.viewDataList = [];

            conditionList.forEach(function (item, i) {
                var key = this.getKey(i);

                this.createItemView(i, key, item);
                this.addViewDataListItem(i, key);
            }, this);
        },

        getGroupOperator: function () {
            if (this.operator === 'or') return 'or';

            return 'and';
        },

        getKey: function (i) {
            return 'view-' + this.level.toString() + '-' + this.number.toString() + '-' + i.toString();
        },

        createItemView: function (number, key, item) {
            this.viewList.push(key);

            item = item || {};

            var additionalData = item.data || {};

            var type = additionalData.type || item.type || 'equals';
            var field = additionalData.field || item.attribute;

            var viewName;
            var fieldType;

            if (~['and', 'or', 'not'].indexOf(type)) {
                viewName = 'views/admin/dynamic-logic/conditions/' + type;
            } else {
                fieldType = this.getMetadata().get(['entityDefs', this.scope, 'fields', field, 'type']);

                if (field === 'id') {
                    fieldType = 'id';
                }

                if (fieldType) {
                    viewName = this.getMetadata().get(['clientDefs', 'DynamicLogic', 'fieldTypes', fieldType, 'view']);
                }

            }

            if (!viewName) {
                return;
            }

            this.createView(key, viewName, {
                itemData: item,
                scope: this.scope,
                level: this.level + 1,
                selector: '[data-view-key="'+key+'"]',
                number: number,
                type: type,
                field: field,
                fieldType: fieldType,
            }, function (view) {
                if (this.isRendered()) {
                    view.render()
                }

                this.controlAddItemVisibility();

                this.listenToOnce(view, 'remove-item', function () {
                    this.removeItem(number);
                }, this);
            }, this);
        },

        fetch: function () {
            var list = [];

            this.viewDataList.forEach(function (item) {
                var view = this.getView(item.key);

                list.push(view.fetch());
            }, this);

            return {
                type: this.operator,
                value: list
            };
        },

        removeItem: function (number) {
            var key = this.getKey(number);
            this.clearView(key);

            this.$el.find('[data-view-key="'+key+'"]').remove();
            this.$el.find('[data-view-ref-key="'+key+'"]').remove();

            var index = -1;
            this.viewDataList.forEach(function (data, i) {
                if (data.index === number) {
                    index = i;
                }
            }, this);
            if (~index) {
                this.viewDataList.splice(index, 1);
            }

            this.controlAddItemVisibility();
        },

        actionAddField: function () {
            this.createView('modal', 'views/admin/dynamic-logic/modals/add-field', {
                scope: this.scope
            }, function (view) {
                view.render();

                this.listenToOnce(view, 'add-field', function (field) {
                    this.addField(field);
                    view.close();
                }, this);
            }, this);
        },

        addField: function (field) {
            var fieldType = this.getMetadata().get(['entityDefs', this.scope, 'fields', field, 'type']);

            if (!fieldType && field == 'id') {
                fieldType = 'id';
            }

            if (!this.getMetadata().get(['clientDefs', 'DynamicLogic', 'fieldTypes', fieldType])) {
                throw new Error();
            }

            var type = this.getMetadata().get(['clientDefs', 'DynamicLogic', 'fieldTypes', fieldType, 'typeList'])[0];

            var i = this.getIndexForNewItem();
            var key = this.getKey(i);

            this.addItemContainer(i);
            this.addViewDataListItem(i, key);

            this.createItemView(i, key, {
                data: {
                    field: field,
                    type: type
                }
            });
        },

        getIndexForNewItem: function () {
            if (!this.viewDataList.length) {
                return 0;
            }

            return (this.viewDataList[this.viewDataList.length - 1]).index + 1;
        },

        addViewDataListItem: function (i, key) {
            this.viewDataList.push({
                index: i,
                key: key,
            });
        },

        addItemContainer: function (i) {
            var $item = $('<div data-view-key="'+this.getKey(i)+'"></div>');
            this.$el.find('> .item-list').append($item);

            var groupOperatorLabel = this.translate(this.getGroupOperator(), 'logicalOperators', 'Admin');

            var $operatorItem = $(
                '<div class="group-operator" data-view-ref-key="' + this.getKey(i)+'">' + groupOperatorLabel +'</div>'
            );

            this.$el.find('> .item-list').append($operatorItem);
        },

        actionAddGroup: function (operator) {
            var i = this.getIndexForNewItem();
            var key = this.getKey(i);

            this.addItemContainer(i);
            this.addViewDataListItem(i, key);

            this.createItemView(i, key, {
                type: operator,
                value: []
            });
        },

        afterRender: function () {
            this.controlAddItemVisibility();
        },

        controlAddItemVisibility: function () {},

    });
});

PK]���7��8views/admin/dynamic-logic/conditions/field-types/enum.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/dynamic-logic/conditions/field-types/enum',
['views/admin/dynamic-logic/conditions/field-types/base'], function (Dep) {

    return Dep.extend({

        fetch: function () {
            var valueView = this.getView('value');

            var item = {
                type: this.type,
                attribute: this.field,
            };

            if (valueView) {
                valueView.fetchToModel();
                item.value = this.model.get(this.field);
            }

            return item;
        },

        getValueViewName: function () {
            var viewName = Dep.prototype.getValueViewName.call(this);

            if (~['in', 'notIn'].indexOf(this.type)) {
                viewName = 'views/fields/multi-enum';
            }

            return viewName;
        },
    });
});
PK]�;n$��?views/admin/dynamic-logic/conditions/field-types/link-parent.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/dynamic-logic/conditions/field-types/link-parent',
['views/admin/dynamic-logic/conditions/field-types/base'], function (Dep) {

    return Dep.extend({

        fetch: function () {
            var valueView = this.getView('value');

            var item;

            if (valueView) {
                valueView.fetchToModel();
            }

            if (this.type === 'equals' || this.type === 'notEquals') {
                var values = {};

                values[this.field + 'Id'] = valueView.model.get(this.field + 'Id');
                values[this.field + 'Name'] = valueView.model.get(this.field + 'Name');
                values[this.field + 'Type'] = valueView.model.get(this.field + 'Type');

                if (this.type === 'equals') {
                    item = {
                        type: 'and',
                        value: [
                            {
                                type: 'equals',
                                attribute: this.field + 'Id',
                                value: valueView.model.get(this.field + 'Id')
                            },
                            {
                                type: 'equals',
                                attribute: this.field + 'Type',
                                value: valueView.model.get(this.field + 'Type')
                            }
                        ],
                        data: {
                            field: this.field,
                            type: 'equals',
                            values: values
                        }
                    };
                } else {
                    item = {
                        type: 'or',
                        value: [
                            {
                                type: 'notEquals',
                                attribute: this.field + 'Id',
                                value: valueView.model.get(this.field + 'Id')
                            },
                            {
                                type: 'notEquals',
                                attribute: this.field + 'Type',
                                value: valueView.model.get(this.field + 'Type')
                            }
                        ],
                        data: {
                            field: this.field,
                            type: 'notEquals',
                            values: values
                        }
                    };
                }
            } else {
                item = {
                    type: this.type,
                    attribute: this.field + 'Id',
                    data: {
                        field: this.field
                    }
                };
            }

            return item;
        },
    });
});
PK]SM\w
w
Aviews/admin/dynamic-logic/conditions/field-types/link-multiple.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/dynamic-logic/conditions/field-types/link-multiple',
['views/admin/dynamic-logic/conditions/field-types/base'], function (Dep) {

    return Dep.extend({

        getValueFieldName: function () {
            return this.name;
        },

        getValueViewName: function () {
            return 'views/fields/link';
        },

        createValueViewContains: function () {
            this.createLinkValueField();
        },

        createValueViewNotContains: function () {
            this.createLinkValueField();
        },

        createLinkValueField: function () {
            var viewName = 'views/fields/link'
            var fieldName = 'link';

            this.createView('value', viewName, {
                model: this.model,
                name: fieldName,
                selector: '.value-container',
                mode: 'edit',
                readOnlyDisabled: true,
                foreignScope: this.getMetadata()
                    .get(['entityDefs', this.scope, 'fields', this.field, 'entity']) ||
                    this.getMetadata().get(['entityDefs', this.scope, 'links', this.field, 'entity']),
            }, function (view) {
                if (this.isRendered()) {
                    view.render();
                }
            }, this);
        },

        fetch: function () {
            var valueView = this.getView('value');

            var item = {
                type: this.type,
                attribute: this.field + 'Ids',
                data: {
                    field: this.field
                },
            };

            if (valueView) {
                valueView.fetchToModel();

                item.value = this.model.get('linkId');

                var values = {};

                values['linkName'] = this.model.get('linkName');
                values['linkId'] = this.model.get('linkId');

                item.data.values = values;
            }

            return item;
        },
    });
});
PK]���""8views/admin/dynamic-logic/conditions/field-types/date.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/dynamic-logic/conditions/field-types/date',
['views/admin/dynamic-logic/conditions/field-types/base'], function (Dep) {

    return Dep.extend({

    });
});
PK]l���8views/admin/dynamic-logic/conditions/field-types/base.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/dynamic-logic/conditions/field-types/base', ['view'], function (Dep) {

    return Dep.extend({

        template: 'admin/dynamic-logic/conditions/field-types/base',

        data: function () {
            return {
                type: this.type,
                field: this.field,
                scope: this.scope,
                typeList: this.typeList,
            };
        },

        events: {
            'click > div > div > [data-action="remove"]': function (e) {
                e.stopPropagation();

                this.trigger('remove-item');
            }
        },

        setup: function () {
            this.type = this.options.type;
            this.field = this.options.field;
            this.scope = this.options.scope;
            this.fieldType = this.options.fieldType;

            this.itemData = this.options.itemData;
            this.additionalData = (this.itemData.data || {});

            this.typeList = this.getMetadata()
                .get(['clientDefs', 'DynamicLogic', 'fieldTypes', this.fieldType, 'typeList']);

            this.wait(true);

            this.getModelFactory().create(this.scope, function (model) {
                this.model = model;
                this.populateValues();

                this.manageValue();

                this.wait(false);
            }, this);
        },

        afterRender: function () {
            this.$type = this.$el.find('select[data-name="type"]');

            this.$type.on('change', function () {
                this.type = this.$type.val();

                this.manageValue();
            }.bind(this));
        },

        populateValues: function () {
            if (this.itemData.attribute) {
                this.model.set(this.itemData.attribute, this.itemData.value);
            }

            this.model.set(this.additionalData.values || {});
        },

        getValueViewName: function () {
            var fieldType = this.getMetadata()
                .get(['entityDefs', this.scope, 'fields', this.field, 'type']) || 'base';

            var viewName = this.getMetadata()
                .get(['entityDefs', this.scope, 'fields', this.field, 'view']) ||
                this.getFieldManager().getViewName(fieldType);

            return viewName;
        },

        getValueFieldName: function () {
            return this.field;
        },

        manageValue: function () {
            var valueType =
                this.getMetadata()
                    .get([
                        'clientDefs',
                        'DynamicLogic',
                        'fieldTypes',
                        this.fieldType,
                        'conditionTypes',
                        this.type,
                        'valueType'
                    ]) ||
                    this.getMetadata()
                        .get(['clientDefs', 'DynamicLogic', 'conditionTypes', this.type, 'valueType']);

            if (valueType === 'field') {
                var viewName = this.getValueViewName();
                var fieldName = this.getValueFieldName();

                this.createView('value', viewName, {
                    model: this.model,
                    name: fieldName,
                    selector: '.value-container',
                    mode: 'edit',
                    readOnlyDisabled: true,
                }, function (view) {
                    if (this.isRendered()) {
                        view.render();
                    }
                }, this);

            }
            else if (valueType === 'custom') {
                this.clearView('value');

                var methodName = 'createValueView' + Espo.Utils.upperCaseFirst(this.type);

                this[methodName]();
            }
            else if (valueType === 'varchar') {
                this.createView('value', 'views/fields/varchar', {
                    model: this.model,
                    name: this.getValueFieldName(),
                    selector: '.value-container',
                    mode: 'edit',
                    readOnlyDisabled: true,
                }, function (view) {
                    if (this.isRendered()) {
                        view.render();
                    }
                }, this);
            }
            else {
                this.clearView('value');
            }
        },

        fetch: function () {
            var valueView = this.getView('value');

            var item = {
                type: this.type,
                attribute: this.field,
            };

            if (valueView) {
                valueView.fetchToModel();

                item.value = this.model.get(this.field);
            }

            return item;
        },
    });
});
PK]N��B��>views/admin/dynamic-logic/conditions/field-types/multi-enum.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/dynamic-logic/conditions/field-types/multi-enum',
['views/admin/dynamic-logic/conditions/field-types/base'], function (Dep) {

    return Dep.extend({

        fetch: function () {
            var valueView = this.getView('value');

            var item = {
                type: this.type,
                attribute: this.field
            };

            if (valueView) {
                valueView.fetchToModel();
                item.value = this.model.get(this.field);
            }

            return item;
        },

        getValueViewName: function () {
            var viewName = Dep.prototype.getValueViewName.call(this);

            if (~['has', 'notHas'].indexOf(this.type)) {
                viewName = 'views/fields/enum';
            }

            return viewName;
        },
    });
});
PK]w�4��8views/admin/dynamic-logic/conditions/field-types/link.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/dynamic-logic/conditions/field-types/link',
['views/admin/dynamic-logic/conditions/field-types/base'], function (Dep) {

    return Dep.extend({

        fetch: function () {
            var valueView = this.getView('value');

            var item = {
                type: this.type,
                attribute: this.field + 'Id',
                data: {
                    field: this.field
                }
            };

            if (valueView) {
                valueView.fetchToModel();
                item.value = this.model.get(this.field + 'Id');

                var values = {};
                values[this.field + 'Name'] = this.model.get(this.field + 'Name');
                item.data.values = values;
            }

            return item;
        },
    });
});
PK]Qj�J&&*views/admin/dynamic-logic/conditions/or.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/dynamic-logic/conditions/or', ['views/admin/dynamic-logic/conditions/group-base'], function (Dep) {

    return Dep.extend({

        operator: 'or',
    });
});
PK]���w+views/admin/dynamic-logic/conditions/not.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/dynamic-logic/conditions/not', ['views/admin/dynamic-logic/conditions/group-base'], function (Dep) {

    return Dep.extend({

        template: 'admin/dynamic-logic/conditions/not',

        operator: 'not',

        data: function () {
            return {
                viewKey: this.viewKey,
                operator: this.operator,
                hasItem: this.hasView(this.viewKey),
                level: this.level,
                groupOperator: this.getGroupOperator(),
            };
        },

        setup: function () {
            this.level = this.options.level || 0;
            this.number = this.options.number || 0;
            this.scope = this.options.scope;

            this.itemData = this.options.itemData || {};
            this.viewList = [];

            var i = 0;
            var key = this.getKey();

            this.createItemView(i, key, this.itemData.value);
            this.viewKey = key;
        },

        removeItem: function () {
            var key = this.getKey();
            this.clearView(key);

            this.controlAddItemVisibility();
        },

        getKey: function () {
            var i = 0;

            return 'view-' + this.level.toString() + '-' + this.number.toString() + '-' + i.toString();
        },

        getIndexForNewItem: function () {
            return 0;
        },

        addItemContainer: function () {},

        addViewDataListItem: function () {},

        fetch: function () {
            var view = this.getView(this.viewKey);

            if (!view) {
                return {
                    type: 'and',
                    value: [],
                };
            }

            var value = view.fetch();

            return {
                type: this.operator,
                value: value,
            };
        },

        controlAddItemVisibility: function () {
            if (this.getView(this.getKey())) {
                this.$el.find(' > .group-bottom').addClass('hidden');
            } else {
                this.$el.find(' > .group-bottom').removeClass('hidden');
            }
        },
    });
});
PK]\ �|/
/
5views/admin/complex-expression/modals/add-function.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/complex-expression/modals/add-function', ['views/modal', 'model'], function (Dep, Model) {

    return Dep.extend({

        template: 'admin/formula/modals/add-function',

        fitHeight: true,

        backdrop: true,

        events: {
            'click [data-action="add"]': function (e) {
                this.trigger('add', $(e.currentTarget).data('value'));
            }
        },

        data: function () {
            var text = this.translate('formulaFunctions', 'messages', 'Admin')
                .replace('{documentationUrl}', this.documentationUrl);
            text = this.getHelper().transformMarkdownText(text, {linksInNewTab: true}).toString();

            return {
                functionDataList: this.functionDataList,
                text: text,
            };
        },

        setup: function () {
            this.header = this.translate('Function');

            this.documentationUrl = 'https://docs.espocrm.com/user-guide/complex-expressions/';

            this.functionDataList = this.options.functionDataList ||
                this.getMetadata().get('app.complexExpression.functionList') || [];
        },

    });
});
PK]|�Tɛ!�!$views/admin/template-manager/edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/template-manager/edit', ['view', 'model'], function (Dep, Model) {

    return Dep.extend({

        template: 'admin/template-manager/edit',

        data: function () {
            return {
                title: this.title,
                hasSubject: this.hasSubject
            };
        },

        events: {
            'click [data-action="save"]': function () {
                this.actionSave();
            },
            'click [data-action="cancel"]': function () {
                this.actionCancel();
            },
            'click [data-action="resetToDefault"]': function () {
                this.actionResetToDefault();
            },
            'keydown.form': function (e) {
                let key = Espo.Utils.getKeyFromKeyEvent(e);

                if (key === 'Control+KeyS' || key === 'Control+Enter') {
                    this.actionSave();

                    e.preventDefault();
                    e.stopPropagation();
                }
            },
        },

        setup: function () {
            this.wait(true);

            this.fullName = this.options.name;

            this.name = this.fullName;
            this.scope = null;

            var arr = this.fullName.split('_');
            if (arr.length > 1) {
                this.scope = arr[1];
                this.name = arr[0];
            }

            this.hasSubject = !this.getMetadata().get(['app', 'templates', this.name, 'noSubject']);

            this.title = this.translate(this.name, 'templates', 'Admin');
            if (this.scope) {
                this.title += ' :: ' + this.translate(this.scope, 'scopeNames');
            }

            this.attributes = {};

            Espo.Ajax.getRequest('TemplateManager/action/getTemplate', {
                name: this.name,
                scope: this.scope
            }).then(function (data) {

                var model = this.model = new Model();
                model.name = 'TemplateManager';
                model.set('body', data.body);
                this.attributes.body = data.body;

                if (this.hasSubject) {
                    model.set('subject', data.subject);
                    this.attributes.subject = data.subject;
                 }

                this.listenTo(model, 'change', function () {
                    this.setConfirmLeaveOut(true);
                }, this);

                this.createView('bodyField', 'views/fields/wysiwyg', {
                    name: 'body',
                    model: model,
                    selector: '.body-field',
                    mode: 'edit'
                });

                if (this.hasSubject) {
                    this.createView('subjectField', 'views/fields/varchar', {
                        name: 'subject',
                        model: model,
                        selector: '.subject-field',
                        mode: 'edit'
                    });
                }

                this.wait(false);
            }.bind(this));
        },

        setConfirmLeaveOut: function (value) {
            this.getRouter().confirmLeaveOut = value;
        },

        afterRender: function () {
            this.$save = this.$el.find('button[data-action="save"]');
            this.$cancel = this.$el.find('button[data-action="cancel"]');
            this.$resetToDefault = this.$el.find('button[data-action="resetToDefault"]');
        },

        actionSave: function () {
            this.$save.addClass('disabled').attr('disabled');
            this.$cancel.addClass('disabled').attr('disabled');
            this.$resetToDefault.addClass('disabled').attr('disabled');

            this.getView('bodyField').fetchToModel();

            var data = {
                name: this.name,
                body: this.model.get('body')
            };
            if (this.scope) {
                data.scope = this.scope;
            }
            if (this.hasSubject) {
                this.getView('subjectField').fetchToModel();
                data.subject = this.model.get('subject');
            }

            Espo.Ui.notify(this.translate('saving', 'messages'));

            Espo.Ajax.postRequest('TemplateManager/action/saveTemplate', data)
            .then(() => {
                this.setConfirmLeaveOut(false);

                this.attributes.body = data.body;
                this.attributes.subject = data.subject;

                this.$save.removeClass('disabled').removeAttr('disabled');
                this.$cancel.removeClass('disabled').removeAttr('disabled');
                this.$resetToDefault.removeClass('disabled').removeAttr('disabled');

                Espo.Ui.success(this.translate('Saved'));
            })
            .catch(() => {
                this.$save.removeClass('disabled').removeAttr('disabled');
                this.$cancel.removeClass('disabled').removeAttr('disabled');
                this.$resetToDefault.removeClass('disabled').removeAttr('disabled');
            });
        },

        actionCancel: function () {
            this.model.set('subject', this.attributes.subject);
            this.model.set('body', this.attributes.body);

            this.setConfirmLeaveOut(false);
        },

        actionResetToDefault: function () {
            this.confirm(this.translate('confirmation', 'messages'), () => {
                this.$save.addClass('disabled').attr('disabled');
                this.$cancel.addClass('disabled').attr('disabled');
                this.$resetToDefault.addClass('disabled').attr('disabled');

                var data = {
                    name: this.name,
                    body: this.model.get('body')
                };

                if (this.scope) {
                    data.scope = this.scope;
                }

                Espo.Ui.notify(this.translate('pleaseWait', 'messages'));

                Espo.Ajax.postRequest('TemplateManager/action/resetTemplate', data)
                    .then(returnData => {
                        this.$save.removeClass('disabled').removeAttr('disabled');
                        this.$cancel.removeClass('disabled').removeAttr('disabled');
                        this.$resetToDefault.removeClass('disabled').removeAttr('disabled');

                        this.attributes.body = returnData.body;
                        this.attributes.subject = returnData.subject;

                        this.model.set('subject', returnData.subject);
                        this.model.set('body', returnData.body);
                        this.setConfirmLeaveOut(false);

                        Espo.Ui.notify(false);
                    })
                    .catch(() => {
                        this.$save.removeClass('disabled').removeAttr('disabled');
                        this.$cancel.removeClass('disabled').removeAttr('disabled');
                        this.$resetToDefault.removeClass('disabled').removeAttr('disabled');
                    });
            });
        },
    });
});
PK]�P��%views/admin/template-manager/index.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/template-manager/index', ['view'], function (Dep) {

    return Dep.extend({

        template: 'admin/template-manager/index',

        data: function () {
            return {
                templateDataList: this.templateDataList,
            };
        },

        events: {
            'click [data-action="selectTemplate"]': function (e) {
                var name = $(e.currentTarget).data('name');

                this.getRouter().checkConfirmLeaveOut(() => {
                    this.selectTemplate(name);
                });
            }
        },

        setup: function () {
            this.templateDataList = [];

            var templateList = Object.keys(this.getMetadata().get(['app', 'templates']) || {});

            templateList.sort((v1, v2) => {
                return this.translate(v1, 'templates', 'Admin')
                    .localeCompare(this.translate(v2, 'templates', 'Admin'));
            });

            templateList.forEach(template =>{
                var defs = this.getMetadata().get(['app', 'templates', template]);

                if (defs.scopeListConfigParam || defs.scopeList) {
                    var scopeList = Espo.Utils.clone(
                        defs.scopeList || this.getConfig().get(defs.scopeListConfigParam) || []);

                    scopeList.sort((v1, v2) => {
                        return this.translate(v1, 'scopeNames')
                            .localeCompare(this.translate(v2, 'scopeNames'));
                    });

                    scopeList.forEach(scope => {
                        let o = {
                            name: template + '_' + scope,
                            text: this.translate(template, 'templates', 'Admin') + ' :: ' +
                                this.translate(scope, 'scopeNames'),
                        };

                        this.templateDataList.push(o);
                    });

                    return;
                }

                var o = {
                    name: template,
                    text: this.translate(template, 'templates', 'Admin'),
                };

                this.templateDataList.push(o);
            });

            this.selectedTemplate = this.options.name;

            if (this.selectedTemplate) {
                this.once('after:render', () => {
                    this.selectTemplate(this.selectedTemplate, true);
                });
            }
        },

        selectTemplate: function (name) {
            this.selectedTemplate = name;

            this.getRouter().navigate('#Admin/templateManager/name=' + this.selectedTemplate, {trigger: false});

            this.createRecordView();

            this.$el.find('[data-action="selectTemplate"]')
                .removeClass('disabled')
                .removeAttr('disabled');

            this.$el.find('[data-name="'+name+'"][data-action="selectTemplate"]')
                .addClass('disabled')
                .attr('disabled', 'disabled');
        },

        createRecordView: function () {
            Espo.Ui.notify(' ... ');

            this.createView('record', 'views/admin/template-manager/edit', {
                selector: '.template-record',
                name: this.selectedTemplate,
            }, (view) => {
                view.render();

                Espo.Ui.notify(false);
                $(window).scrollTop(0);
            });
        },

        updatePageTitle: function () {
            this.setPageTitle(this.getLanguage().translate('Template Manager', 'labels', 'Admin'));
        },
    });
});
PK]J4Q��views/admin/notifications.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/notifications', ['views/settings/record/edit'], function (Dep) {

    return Dep.extend({

        layoutName: 'notifications',

        saveAndContinueEditingAction: false,

        dynamicLogicDefs: {
            fields: {
                assignmentEmailNotificationsEntityList: {
                    visible: {
                        conditionGroup: [
                            {
                                type: 'isTrue',
                                attribute: 'assignmentEmailNotifications',
                            }
                        ],
                    },
                },
                adminNotificationsNewVersion: {
                    visible: {
                        conditionGroup: [
                            {
                                type: 'isTrue',
                                attribute: 'adminNotifications',
                            }
                        ],
                    },
                },
                adminNotificationsNewExtensionVersion: {
                    visible: {
                        conditionGroup: [
                            {
                                type: 'isTrue',
                                attribute: 'adminNotifications',
                            }
                        ],
                    },
                },
            },
        },

        setup: function () {
            Dep.prototype.setup.call(this);

            this.controlStreamEmailNotificationsEntityList();
            this.listenTo(this.model, 'change', function (model) {
                if (model.hasChanged('streamEmailNotifications') || model.hasChanged('portalStreamEmailNotifications')) {
                    this.controlStreamEmailNotificationsEntityList();
                }
            }, this);
        },

        controlStreamEmailNotificationsEntityList: function () {
            if (this.model.get('streamEmailNotifications') || this.model.get('portalStreamEmailNotifications')) {
                this.showField('streamEmailNotificationsEntityList');
                this.showField('streamEmailNotificationsTypeList');
            } else {
                this.hideField('streamEmailNotificationsEntityList');
                this.hideField('streamEmailNotificationsTypeList');
            }
        }

    });
});
PK]�����views/admin/authentication.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/authentication', ['views/settings/record/edit'], function (Dep) {

    return Dep.extend({

        layoutName: 'authentication',

        saveAndContinueEditingAction: false,

        setup: function () {
            this.methodList = [];

            let defs = this.getMetadata().get(['authenticationMethods']) || {};

            for (let method in defs) {
                if (defs[method].settings && defs[method].settings.isAvailable) {
                    this.methodList.push(method);
                }
            }

            this.authFields = {};

            Dep.prototype.setup.call(this);

            this.handlePanelsVisibility();

            this.listenTo(this.model, 'change:authenticationMethod', () => {
                this.handlePanelsVisibility();
            });

            this.manage2FAFields();

            this.listenTo(this.model, 'change:auth2FA', () => {
                this.manage2FAFields();
            });

            this.managePasswordRecoveryFields();

            this.listenTo(this.model, 'change:passwordRecoveryDisabled', () => {
                this.managePasswordRecoveryFields();
            });
        },

        setupBeforeFinal: function () {
            this.dynamicLogicDefs = {
                fields: {},
                panels: {},
            };

            this.methodList.forEach(method => {
                let fieldList = this.getMetadata().get(['authenticationMethods', method, 'settings', 'fieldList']);

                if (fieldList) {
                    this.authFields[method] = fieldList;
                }

                let mDynamicLogicFieldsDefs = this.getMetadata()
                    .get(['authenticationMethods', method, 'settings', 'dynamicLogic', 'fields']);

                if (mDynamicLogicFieldsDefs) {
                    for (let f in mDynamicLogicFieldsDefs) {
                        this.dynamicLogicDefs.fields[f] = Espo.Utils.cloneDeep(mDynamicLogicFieldsDefs[f]);
                    }
                }
            });

            Dep.prototype.setupBeforeFinal.call(this);
        },

        modifyDetailLayout: function (layout) {
            this.methodList.forEach(method => {
                let mLayout = this.getMetadata().get(['authenticationMethods', method, 'settings', 'layout']);

                if (!mLayout) {
                    return;
                }

                mLayout = Espo.Utils.cloneDeep(mLayout);
                mLayout.name = method;

                this.prepareLayout(mLayout, method);

                layout.push(mLayout);
            });
        },

        prepareLayout: function (layout, method) {
            layout.rows.forEach(row => {
                row
                    .filter(item => !item.noLabel && !item.labelText && item.name)
                    .forEach(item => {
                        let labelText = this.translate(item.name, 'fields', 'Settings');

                        if (labelText && labelText.toLowerCase().indexOf(method.toLowerCase() + ' ') === 0) {
                            item.labelText = labelText.substring(method.length + 1);
                        }
                    });
            });
        },

        handlePanelsVisibility: function () {
            var authenticationMethod = this.model.get('authenticationMethod');

            this.methodList.forEach(method => {
                var fieldList = (this.authFields[method] || []);

                if (method !== authenticationMethod) {
                    this.hidePanel(method);

                    fieldList.forEach(field => {
                        this.hideField(field);
                    });

                    return;
                }

                this.showPanel(method);

                fieldList.forEach(field => {
                    this.showField(field);
                });

                this.processDynamicLogic();
            });
        },

        manage2FAFields: function () {
            if (this.model.get('auth2FA')) {
                this.showField('auth2FAForced');
                this.showField('auth2FAMethodList');
                this.showField('auth2FAInPortal');
                this.setFieldRequired('auth2FAMethodList');

                return;
            }

            this.hideField('auth2FAForced');
            this.hideField('auth2FAMethodList');
            this.hideField('auth2FAInPortal');
            this.setFieldNotRequired('auth2FAMethodList');
        },

        managePasswordRecoveryFields: function () {
            if (!this.model.get('passwordRecoveryDisabled')) {
                this.showField('passwordRecoveryForAdminDisabled');
                this.showField('passwordRecoveryForInternalUsersDisabled');
                this.showField('passwordRecoveryNoExposure');

                return;
            }

            this.hideField('passwordRecoveryForAdminDisabled');
            this.hideField('passwordRecoveryForInternalUsersDisabled');
            this.hideField('passwordRecoveryNoExposure');
        },
    });
});
PK]@�D��(views/admin/system-requirements/index.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/system-requirements/index', ['view'], function (Dep) {

    return Dep.extend({

        template: 'admin/system-requirements/index',

        data: function () {
            return {
                phpRequirementList: this.requirementList.php,
                databaseRequirementList: this.requirementList.database,
                permissionRequirementList: this.requirementList.permission,
            };
        },

        setup: function () {
            this.requirementList = [];

            Espo.Ajax.getRequest('Admin/action/systemRequirementList').then(requirementList => {
                this.requirementList = requirementList;

                if (this.isRendered() || this.isBeingRendered()) {
                    this.reRender();
                }
            });
        },
    });
});
PK]�+�,�
�
'views/admin/formula/fields/attribute.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/formula/fields/attribute', ['views/fields/multi-enum', 'ui/multi-select'],
function (Dep, /** module:ui/multi-select */MultiSelect) {

    return Dep.extend({

        setupOptions: function () {
            Dep.prototype.setupOptions.call(this);

            if (this.options.attributeList) {
                this.params.options = this.options.attributeList;

                return;
            }

            const attributeList = this.getFieldManager()
                .getEntityTypeAttributeList(this.options.scope)
                .concat(['id'])
                .sort();

            const links = this.getMetadata().get(['entityDefs', this.options.scope, 'links']) || {};

            const linkList = [];

            Object.keys(links).forEach(link => {
                const type = links[link].type;
                const scope = links[link].entity;

                if (!type) {
                    return;
                }

                if (!scope) {
                    return;
                }

                if (
                    links[link].disabled ||
                    links[link].utility
                ) {
                    return;
                }

                if (~['belongsToParent', 'hasOne', 'belongsTo'].indexOf(type)) {
                    linkList.push(link);
                }
            });

            linkList.sort();

            linkList.forEach(link => {
                const scope = links[link].entity;

                let linkAttributeList = this.getFieldManager()
                    .getEntityTypeAttributeList(scope)
                    .sort();

                linkAttributeList.forEach(item => {
                    attributeList.push(link + '.' + item);
                });
            });

            this.params.options = attributeList;
        },

        afterRender: function () {
            Dep.prototype.afterRender.call(this);

            if (this.$element) {
                MultiSelect.focus(this.$element);
            }
        },
    });
});
PK]V��}

+views/admin/formula/modals/add-attribute.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/formula/modals/add-attribute', ['views/modal', 'model'], function (Dep, Model) {

    return Dep.extend({

        templateContent: '<div class="attribute" data-name="attribute">{{{attribute}}}</div>',

        backdrop: true,

        setup: function () {
            this.header = this.translate('Attribute');
            this.scope = this.options.scope;

            var model = new Model();

            this.createView('attribute', 'views/admin/formula/fields/attribute', {
                selector: '[data-name="attribute"]',
                model: model,
                mode: 'edit',
                scope: this.scope,
                defs: {
                    name: 'attribute',
                    params: {}
                },
                attributeList: this.options.attributeList,
            }, view => {
                this.listenTo(view, 'change', () => {
                    var list = model.get('attribute') || [];

                    if (!list.length) {
                        return;
                    }

                    this.trigger('add', list[0]);
                });
            });
        },

    });
});
PK]N�
�

*views/admin/formula/modals/add-function.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/formula/modals/add-function', ['views/modal', 'model'], function (Dep, Model) {

    return Dep.extend({

        template: 'admin/formula/modals/add-function',

        fitHeight: true,

        backdrop: true,

        events: {
            'click [data-action="add"]': function (e) {
                this.trigger('add', $(e.currentTarget).data('value'));
            }
        },

        data: function () {
            var text = this.translate('formulaFunctions', 'messages', 'Admin')
                .replace('{documentationUrl}', this.documentationUrl);
            text = this.getHelper().transformMarkdownText(text, {linksInNewTab: true}).toString();

            return {
                functionDataList: this.functionDataList,
                text: text,
            };
        },

        setup: function () {
            this.header = this.translate('Function');

            this.documentationUrl = 'https://docs.espocrm.com/administration/formula/';

            this.functionDataList = this.options.functionDataList ||
                this.getMetadata().get('app.formula.functionList') || [];
        },

    });
});
PK]���U��views/admin/settings.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/settings', ['views/settings/record/edit'], function (Dep) {

    return Dep.extend({

        layoutName: 'settings',

        saveAndContinueEditingAction: false,

        setup: function () {
            Dep.prototype.setup.call(this);

            if (this.getHelper().getAppParam('isRestrictedMode') && !this.getUser().isSuperAdmin()) {
                this.hideField('cronDisabled');
                this.hideField('maintenanceMode');
                this.setFieldReadOnly('useWebSocket');
                this.setFieldReadOnly('siteUrl');
            }
        },
    });
});
PK]R���views/admin/inbound-emails.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/inbound-emails', ['views/settings/record/edit'], function (Dep) {

    return Dep.extend({

        layoutName: 'inboundEmails',

        saveAndContinueEditingAction: false,

        setup: function () {
            Dep.prototype.setup.call(this);
        },

        afterRender: function () {
            Dep.prototype.afterRender.call(this);
        },

    });
});
PK]�%cB��views/admin/currency.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/currency', ['views/settings/record/edit'], function (Dep) {

    return Dep.extend({

        layoutName: 'currency',

        saveAndContinueEditingAction: false,

        setup: function () {
            Dep.prototype.setup.call(this);

            this.listenTo(this.model, 'change:currencyList', function (model, value, o) {
                if (!o.ui) {
                    return;
                }

                var currencyList = Espo.Utils.clone(model.get('currencyList'));

                this.setFieldOptionList('defaultCurrency', currencyList);
                this.setFieldOptionList('baseCurrency', currencyList);

                this.controlCurrencyRatesVisibility();
            }, this);

            this.listenTo(this.model, 'change', function (model, o) {
                if (!o.ui) {
                    return;
                }

                if (model.hasChanged('currencyList') || model.hasChanged('baseCurrency')) {
                    var currencyRatesField = this.getFieldView('currencyRates');

                    if (currencyRatesField) {
                        currencyRatesField.reRender();
                    }
                }
            }, this);

            this.controlCurrencyRatesVisibility();
        },

        controlCurrencyRatesVisibility: function () {
            var currencyList = this.model.get('currencyList');

            if (currencyList.length < 2) {
                this.hideField('currencyRates');
            } else {
                this.showField('currencyRates');
            }
        },

    });
});
PK]����#views/admin/field-manager/header.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/field-manager/header', ['view'], function (Dep) {

    return Dep.extend({

        template: 'admin/field-manager/header',

        data: function () {
            return {
                scope: this.scope,
                field: this.field,
            };
        },

        setup: function () {
            this.scope = this.options.scope;
            this.field = this.options.field;
        },

        setField: function (field) {
            this.field = field;

            if (this.isRendered()) {
                this.reRender();
            }
        },
    });
});
PK]H*�A0views/admin/field-manager/fields/date/default.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/field-manager/fields/date/default', ['views/fields/enum'], function (Dep) {

    return Dep.extend({

        fetch: function () {
            var data = Dep.prototype.fetch.call(this);

            if (data[this.name] === '') {
                data[this.name] = null;
            }

            return data;
        },

        setupOptions: function () {
            Dep.prototype.setupOptions.call(this);

            var value = this.model.get(this.name);

            if (this.params.options && value && !~(this.params.options).indexOf(value)) {
                this.params.options.push(value);
            }
        },
    });
});
PK]ʏ:��5views/admin/field-manager/fields/date/after-before.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/field-manager/fields/date/after-before', ['views/fields/varchar'], function (Dep) {

    return Dep.extend({

        setupOptions: function () {
            Dep.prototype.setupOptions.call(this);

            if (!this.model.scope) {
                return;
            }

            var list = this.getFieldManager().getEntityTypeFieldList(
                this.model.scope,
                {
                    typeList: ['date', 'datetime', 'datetimeOptional'],
                }
            );

            if (this.model.get('name')) {
                list = list.filter(function (item) {
                    return item !== this.model.get('name');
                }, this);
            }

            this.params.options = list;
        },

    });
});
PK]4�E:bb+views/admin/field-manager/fields/options.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/field-manager/fields/options', ['views/fields/array'], function (Dep) {

    return Dep.extend({

        maxItemLength: 100,

        setup: function () {
            Dep.prototype.setup.call(this);

            this.translatedOptions = {};

            let list = this.model.get(this.name) || [];

            list.forEach(value => {
                this.translatedOptions[value] = this.getLanguage()
                    .translateOption(value, this.options.field, this.options.scope);
            });

            this.model.fetchedAttributes.translatedOptions = this.translatedOptions;
        },

        getItemHtml: function (value) {
            // Do not use the `html` method to avoid XSS.

            let text = (this.translatedOptions[value] || value);

            let $div = $('<div>')
                .addClass('list-group-item link-with-role form-inline')
                .attr('data-value', value)
                .append(
                    $('<div>')
                        .addClass('pull-left item-content')
                        .css('width', '92%')
                        .css('display', 'inline-block')
                        .append(
                            $('<input>')
                                .attr('type', 'text')
                                .attr('data-name', 'translatedValue')
                                .attr('data-value', value)
                                .addClass('role form-control input-sm pull-right')
                                .attr('value', text)
                                .css('width', 'auto')
                        )
                        .append(
                            $('<div>')
                                .addClass('item-text')
                                .text(value)
                        )
                )
                .append(
                    $('<div>')
                        .css('width', '8%')
                        .css('display', 'inline-block')
                        .css('vertical-align', 'top')
                        .append(
                            $('<a>')
                                .attr('role', 'button')
                                .attr('tabindex', '0')
                                .addClass('pull-right')
                                .attr('data-value', value)
                                .attr('data-action', 'removeValue')
                                .append(
                                    $('<span>').addClass('fas fa-times')
                                )
                        )
                )
                .append(
                    $('<br>').css('clear', 'both')
                );

            return $div.get(0).outerHTML;
        },

        fetch: function () {
            let data = Dep.prototype.fetch.call(this);

            if (!data[this.name].length) {
                data[this.name] = null;
                data.translatedOptions = {};

                return data;
            }

            data.translatedOptions = {};

            (data[this.name] || []).forEach(value => {
                let valueInternal = value.replace(/"/g, '\\"');

                let translatedValue = this.$el
                    .find('input[data-name="translatedValue"][data-value="'+valueInternal+'"]').val() || value;

                data.translatedOptions[value] = translatedValue.toString();
            });

            return data;
        },
    });
});
PK]ocu�111views/admin/field-manager/fields/phone/default.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/field-manager/fields/phone/default', ['views/fields/enum'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            this.setOptionList(this.model.get('typeList') || ['']);

            this.listenTo(this.model, 'change:typeList', () => {
                this.setOptionList(this.model.get('typeList') || ['']);
            });
        }
    });
});
PK]
�U�
�
<views/admin/field-manager/fields/dynamic-logic-conditions.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/field-manager/fields/dynamic-logic-conditions', ['views/fields/base'], function (Dep) {

    return Dep.extend({

        detailTemplate: 'admin/field-manager/fields/dynamic-logic-conditions/detail',
        editTemplate: 'admin/field-manager/fields/dynamic-logic-conditions/edit',

        events: {
            'click [data-action="editConditions"]': function () {
                this.edit();
            }
        },

        data: function () {
        },

        setup: function () {
            this.conditionGroup = Espo.Utils.cloneDeep((this.model.get(this.name) || {}).conditionGroup || []);

            this.scope = this.params.scope || this.options.scope;

            this.createStringView();
        },

        createStringView: function () {
            this.createView('conditionGroup', 'views/admin/dynamic-logic/conditions-string/group-base', {
                selector: '.top-group-string-container',
                itemData: {
                    value: this.conditionGroup
                },
                operator: 'and',
                scope: this.scope,
            }, (view) => {
                if (this.isRendered()) {
                    view.render();
                }
            });
        },

        edit: function () {
            this.createView('modal', 'views/admin/dynamic-logic/modals/edit', {
                conditionGroup: this.conditionGroup,
                scope: this.scope,
            }, (view) => {
                view.render();

                this.listenTo(view, 'apply', (conditionGroup) => {
                    this.conditionGroup = conditionGroup;

                    this.trigger('change');

                    this.createStringView();
                });
            });
        },

        fetch: function () {
            var data = {};

            data[this.name] = {
                conditionGroup: this.conditionGroup,
            };

            if (this.conditionGroup.length === 0) {
                data[this.name] = null;
            }

            return data;
        },
    });
});
PK]���|9views/admin/field-manager/fields/dynamic-logic-options.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/field-manager/fields/dynamic-logic-options', ['views/fields/base', 'model'], function (Dep, Model) {

    return Dep.extend({

        editTemplate: 'admin/field-manager/fields/dynamic-logic-options/edit',

        events: {
            'click [data-action="editConditions"]': function (e) {
                var index = parseInt($(e.currentTarget).data('index'));

                this.edit(index);
            },
            'click [data-action="addOptionList"]': function (e) {
                this.addOptionList();
            },
            'click [data-action="removeOptionList"]': function (e) {
                var index = parseInt($(e.currentTarget).data('index'));
                this.removeItem(index);
            }
        },

        data: function () {
            return {
                itemDataList: this.itemDataList
            };
        },

        setup: function () {
            this.optionsDefsList = Espo.Utils.cloneDeep(this.model.get(this.name)) || []
            this.scope = this.options.scope;

            this.setupItems();
            this.setupItemViews();
        },

        setupItems: function () {
            this.itemDataList = [];

            this.optionsDefsList.forEach((item, i) => {
                this.itemDataList.push({
                    conditionGroupViewKey: 'conditionGroup' + i.toString(),
                    optionsViewKey: 'options' + i.toString(),
                    index: i,
                });
            });
        },

        setupItemViews: function () {
            this.optionsDefsList.forEach((item, i) => {
                this.createStringView(i);

                this.createOptionsView(i);
            });
        },

        createOptionsView: function (num) {
            var key = 'options' + num.toString();

            if (!this.optionsDefsList[num]) {
                return;
            }

            var model = new Model();

            model.set('options', this.optionsDefsList[num].optionList || []);

            this.createView(key, 'views/fields/multi-enum', {
                selector: '.options-container[data-key="'+key+'"]',
                model: model,
                name: 'options',
                mode: 'edit',
                params: {
                    options: this.model.get('options'),
                    translatedOptions: this.model.get('translatedOptions')
                }
            }, (view) => {
                if (this.isRendered()) {
                    view.render();
                }

                this.listenTo(this.model, 'change:options', () => {
                    view.setTranslatedOptions(this.getTranslatedOptions());

                    view.setOptionList(this.model.get('options'));
                });

                this.listenTo(model, 'change', () => {
                    this.optionsDefsList[num].optionList = model.get('options') || [];
                });
            });
        },

        getTranslatedOptions: function () {
            if (this.model.get('translatedOptions')) {
                return this.model.get('translatedOptions');
            }

            var translatedOptions = {};

            var list = this.model.get('options') || [];

            list.forEach((value) => {
                translatedOptions[value] = this.getLanguage()
                    .translateOption(value, this.options.field, this.options.scope);
            });

            return translatedOptions;
        },

        createStringView: function (num) {
            var key = 'conditionGroup' + num.toString();

            if (!this.optionsDefsList[num]) {
                return;
            }

            this.createView(key, 'views/admin/dynamic-logic/conditions-string/group-base', {
                selector: '.string-container[data-key="'+key+'"]',
                itemData: {
                    value: this.optionsDefsList[num].conditionGroup
                },
                operator: 'and',
                scope: this.scope,
            }, (view) => {
                if (this.isRendered()) {
                    view.render();
                }
            });
        },

        edit: function (num) {
            this.createView('modal', 'views/admin/dynamic-logic/modals/edit', {
                conditionGroup: this.optionsDefsList[num].conditionGroup,
                scope: this.options.scope,
            }, (view) => {
                view.render();

                this.listenTo(view, 'apply', (conditionGroup) => {
                    this.optionsDefsList[num].conditionGroup = conditionGroup;

                    this.trigger('change');

                    this.createStringView(num);
                });
            });
        },

        addOptionList: function () {
            var i = this.itemDataList.length;

            this.optionsDefsList.push({
                optionList: this.model.get('options') || [],
                conditionGroup: null,
            });

            this.setupItems();
            this.reRender();
            this.setupItemViews();

            this.trigger('change');
        },

        removeItem: function (num) {
            this.optionsDefsList.splice(num, 1);

            this.setupItems();
            this.reRender();
            this.setupItemViews();

            this.trigger('change');
        },

        fetch: function () {
            var data = {};

            data[this.name] = this.optionsDefsList;

            if (!this.optionsDefsList.length) {
                data[this.name] = null;
            }

            return data;
        },

    });
});
PK]�ك���1views/admin/field-manager/fields/foreign/field.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/field-manager/fields/foreign/field', ['views/fields/enum'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            if (!this.model.isNew()) {
                this.setReadOnly(true);
            }

            this.listenTo(this.model, 'change:field', () => {
                this.manageField();
            });

            this.viewValue = this.model.get('view');
        },

        setupOptions: function () {
            this.listenTo(this.model, 'change:link', () => {
                this.setupOptionsByLink();
                this.reRender();
            });

            this.setupOptionsByLink();
        },

        setupOptionsByLink: function () {
            this.typeList = this.getMetadata().get(['fields', 'foreign', 'fieldTypeList']);

            var link = this.model.get('link');

            if (!link) {
                this.params.options = [''];

                return;
            }

            var scope = this.getMetadata().get(['entityDefs', this.options.scope, 'links', link, 'entity']);

            if (!scope) {
                this.params.options = [''];

                return;
            }

            var fields = this.getMetadata().get(['entityDefs', scope, 'fields']) || {};

            this.params.options = Object.keys(Espo.Utils.clone(fields)).filter(item => {
                var type = fields[item].type;

                if (!~this.typeList.indexOf(type)) {
                    return;
                }

                if (
                    fields[item].disabled ||
                    fields[item].utility ||
                    fields[item].directAccessDisabled ||
                    fields[item].notStorable
                ) {
                    return;
                }

                return true;
            });

            this.translatedOptions = {};

            this.params.options.forEach(item => {
                this.translatedOptions[item] = this.translate(item, 'fields', scope);
            });

            this.params.options = this.params.options.sort((v1, v2) => {
                return this.translate(v1, 'fields', scope).localeCompare(this.translate(v2, 'fields', scope));
            });

            this.params.options.unshift('');
        },

        manageField: function () {
            if (!this.model.isNew()) {
                return;
            }

            var link = this.model.get('link');
            var field = this.model.get('field');

            if (!link || !field) {
                return;
            }

            var scope = this.getMetadata().get(['entityDefs', this.options.scope, 'links', link, 'entity']);

            if (!scope) {
                return;
            }

            var type = this.getMetadata().get(['entityDefs', scope, 'fields', field, 'type']);

            this.viewValue = this.getMetadata().get(['fields', 'foreign', 'fieldTypeViewMap', type]);
        },

        fetch: function () {
            var data = Dep.prototype.fetch.call(this);

            if (this.model.isNew()) {
                if (this.viewValue) {
                    data['view'] = this.viewValue;
                }
            }

            return data;
        },
    });
});
PK]�I�7

0views/admin/field-manager/fields/foreign/link.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/field-manager/fields/foreign/link', ['views/fields/enum'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            if (!this.model.isNew()) {
                this.setReadOnly(true);
            }
        },

        setupOptions: function () {
            var links = this.getMetadata().get(['entityDefs', this.options.scope, 'links']) || {};

            this.params.options = Object.keys(Espo.Utils.clone(links)).filter((item) => {
                if (links[item].type !== 'belongsTo' && links[item].type !== 'hasOne') {
                    return;
                }

                if (links[item].noJoin) {
                    return;
                }

                if (links[item].disabled) {
                    return;
                }

                return true;
            });

            var scope = this.options.scope;

            this.translatedOptions = {};

            this.params.options.forEach((item) => {
                this.translatedOptions[item] = this.translate(item, 'links', scope);
            });

            this.params.options = this.params.options.sort((v1, v2) => {
                return this.translate(v1, 'links', scope).localeCompare(this.translate(v2, 'links', scope));
            });

            this.params.options.unshift('');
        },
    });
});
PK]��1�	�	3views/admin/field-manager/fields/options/default.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/field-manager/fields/options/default', ['views/fields/enum'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            this.validations.push('listed');

            this.setOptionList(this.model.get('options') || ['']);

            this.listenTo(this.model, 'change:options', () => {
                this.setOptionList(this.model.get('options') || ['']);
            });
        },

        validateListed: function () {
            let value = this.model.get(this.name) ?? '';

            if (!this.params.options) {
                return false;
            }

            let options = this.model.get('options') || [''];

            if (options.indexOf(value) === -1) {
                let msg = this.translate('fieldInvalid', 'messages')
                    .replace('{field}', this.getLabelText());

                this.showValidationMessage(msg);

                return true;
            }

            return false;
        },
    });
});
PK]�7���/views/admin/field-manager/fields/entity-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/field-manager/fields/entity-list', ['views/fields/entity-type-list'], function (Dep) {

    return Dep.extend({
    });
});
PK]���ݤ
�
0views/admin/field-manager/fields/link/default.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/field-manager/fields/link/default', ['views/fields/link'], function (Dep) {

    return Dep.extend({

        data: function () {
            var defaultAttributes = this.model.get('defaultAttributes') || {};
            var nameValue = defaultAttributes[this.options.field + 'Name'] || null;
            var idValue = defaultAttributes[this.options.field + 'Id'] || null;

            var data = Dep.prototype.data.call(this);

            data.nameValue = nameValue;
            data.idValue = idValue;

            return data;
        },

        setup: function () {
            Dep.prototype.setup.call(this);

            this.foreignScope = this.getMetadata()
                .get(['entityDefs', this.options.scope, 'links', this.options.field, 'entity']);
        },

        fetch: function () {
            var data = Dep.prototype.fetch.call(this);

            var defaultAttributes = {};
            defaultAttributes[this.options.field + 'Id'] = data[this.idName];
            defaultAttributes[this.options.field + 'Name'] = data[this.nameName];

            if (data[this.idName] === null) {
                defaultAttributes = null;
            }

            return {
                defaultAttributes: defaultAttributes
            };
        },
    });
});
PK]�o$�4views/admin/field-manager/fields/currency-default.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/field-manager/fields/currency-default', ['views/fields/enum'], function (Dep) {

    return Dep.extend({

        fetchEmptyValueAsNull: true,

        setupOptions: function () {
            this.params.options = [''];

            (this.getConfig().get('currencyList') || []).forEach(item => {
                this.params.options.push(item);
            });
        },
    });
});
PK]�G%�LL+views/admin/field-manager/fields/pattern.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/field-manager/fields/pattern', ['views/fields/varchar'], function (Dep) {

    /**
     * @class
     * @name Class
     * @memberOf module:views/admin/field-manager/fields/pattern
     * @extends module:views/fields/varchar
     */
    return Dep.extend(/** @lends module:views/admin/field-manager/fields/pattern.Class# */{

        noSpellCheck: true,

        setupOptions: function () {
            let patterns = this.getMetadata().get(['app', 'regExpPatterns']) || {};

            let patternList = Object.keys(patterns)
                .filter(item => !patterns[item].isSystem)
                .map(item => '$' + item);

            this.setOptionList(patternList);
        },
    })
});
PK]�H±yy5views/admin/field-manager/fields/options-reference.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/field-manager/fields/options-reference', ['views/fields/enum'], function (Dep) {

    return Dep.extend({

        enumFieldTypeList: [
            'enum',
            'multiEnum',
            'array',
            'checklist',
            'varchar',
        ],

        setupOptions: function () {
            this.params.options = [''];

            let entityTypeList = Object.keys(this.getMetadata().get(['entityDefs']))
                .filter(item => this.getMetadata().get(['scopes', item, 'object']))
                .sort((s1, s2) => {
                    return this.getLanguage().translate(s1, 'scopesName')
                        .localeCompare(this.getLanguage().translate(s2, 'scopesName'));
                });

            this.translatedOptions = {};

            entityTypeList.forEach(entityType => {
                let fieldList =
                    Object.keys(this.getMetadata().get(['entityDefs', entityType, 'fields']) || [])
                        .filter(item => entityType !== this.model.scope || item !== this.model.get('name'))
                        .sort((s1, s2) => {
                            return this.getLanguage().translate(s1, 'fields', entityType)
                                .localeCompare(this.getLanguage().translate(s2, 'fields', entityType));
                        });

                fieldList.forEach(field => {
                    let {type, options, optionsPath, optionsReference} =
                        this.getMetadata().get(['entityDefs', entityType, 'fields', field]) || {};

                    if (!this.enumFieldTypeList.includes(type)) {
                        return;
                    }

                    if (optionsPath || optionsReference) {
                        return;
                    }

                    if (!options) {
                        return;
                    }

                    let value = entityType + '.' + field;

                    this.params.options.push(value);

                    this.translatedOptions[value] =
                        this.translate(entityType, 'scopeName') + ' · ' +
                        this.translate(field, 'fields', entityType);
                });
            });
        },
    });
});
PK]�ɔa^^9views/admin/field-manager/fields/link-multiple/default.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/field-manager/fields/link-multiple/default', ['views/fields/link-multiple'], function (Dep) {

    return Dep.extend({

        data: function () {
            var defaultAttributes = this.model.get('defaultAttributes') || {};

            var nameHash = defaultAttributes[this.options.field + 'Names'] || {};
            var idValues = defaultAttributes[this.options.field + 'Ids'] || [];

            var data = Dep.prototype.data.call(this);

            data.nameHash = nameHash;
            data.idValues = idValues;

            return data;
        },

        setup: function () {
            Dep.prototype.setup.call(this);

            this.foreignScope = this.getMetadata()
                .get(['entityDefs', this.options.scope, 'links', this.options.field, 'entity']);
        },

        fetch: function () {
            var data = Dep.prototype.fetch.call(this);

            var defaultAttributes = {};

            defaultAttributes[this.options.field + 'Ids'] = data[this.idsName];
            defaultAttributes[this.options.field + 'Names'] = data[this.nameHashName];

            if (data[this.idsName] === null || data[this.idsName].length === 0) {
                defaultAttributes = null;
            }

            return {
                defaultAttributes: defaultAttributes
            };
        },

        copyValuesFromModel: function () {
            var defaultAttributes = this.model.get('defaultAttributes') || {};

            var idValues = defaultAttributes[this.options.field + 'Ids'] || [];
            var nameHash = defaultAttributes[this.options.field + 'Names'] || {};

            this.ids = idValues;
            this.nameHash = nameHash;
        },
    });
});
PK]c�O�mm/views/admin/field-manager/fields/source-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/field-manager/fields/source-list', ['views/fields/multi-enum'], function (Dep) {

    return Dep.extend({

        setupOptions: function () {
            this.params.options = Espo.Utils.clone(this.getMetadata().get('entityDefs.Attachment.sourceList') || []);

            this.translatedOptions = {};

            this.params.options.forEach(item => {
                this.translatedOptions[item] = this.translate(item, 'scopeNamesPlural');
            });
        }
    });
});
PK]�'����6views/admin/field-manager/fields/options-with-style.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/field-manager/fields/options-with-style', ['views/admin/field-manager/fields/options'],
function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            this.optionsStyleMap = this.model.get('style') || {};

            this.styleList = [
                'default',
                'success',
                'danger',
                'warning',
                'info',
                'primary',
            ];

            this.events['click [data-action="selectOptionItemStyle"]'] = (e) => {
                let $target = $(e.currentTarget);
                let style = $target.data('style');
                let value = $target.data('value').toString();

                this.changeStyle(value, style);
            };
        },

        changeStyle: function (value, style) {
            let valueInternal = value.replace(/"/g, '\\"');

            this.$el
                .find('[data-action="selectOptionItemStyle"][data-value="' + valueInternal + '"] .check-icon')
                .addClass('hidden');

            this.$el
                .find('[data-action="selectOptionItemStyle"][data-value="' + valueInternal + '"]' +
                    '[data-style="'+style+'"] .check-icon')
                .removeClass('hidden');

            let $item = this.$el.find('.list-group-item[data-value="' + valueInternal + '"]').find('.item-text');

            this.styleList.forEach(item => {
                $item.removeClass('text-' + item);
            });

            $item.addClass('text-' + style);

            if (style === 'default') {
                style = null;
            }

            this.optionsStyleMap[value] = style;
        },

        getItemHtml: function (value) {
            // Do not use the `html` method to avoid XSS.

            let html = Dep.prototype.getItemHtml.call(this, value);

            let styleList = this.styleList;
            let styleMap = this.optionsStyleMap;

            let style = 'default';
            let $liList = [];

            styleList.forEach(item => {
                let isHidden = true;

                if (styleMap[value] === item) {
                    style = item;
                    isHidden = false;
                }
                else {
                    if (item === 'default' && !styleMap[value]) {
                        isHidden = false;
                    }
                }

                let text = this.getLanguage().translateOption(item, 'style', 'LayoutManager');

                let $li = $('<li>')
                    .append(
                        $('<a>')
                            .attr('role', 'button')
                            .attr('tabindex', '0')
                            .attr('data-action', 'selectOptionItemStyle')
                            .attr('data-style', item)
                            .attr('data-value', value)
                            .append(
                                $('<span>')
                                    .addClass('check-icon fas fa-check pull-right')
                                    .addClass(isHidden ? 'hidden' : ''),
                                $('<div>')
                                    .addClass('text-' + item)
                                    .text(text)
                            )
                    );

                $liList.push($li);
            });

            let $dropdown = $('<div>')
                .addClass('btn-group pull-right')
                .append(
                    $('<button>')
                        .addClass('btn btn-link btn-sm dropdown-toggle')
                        .attr('type', 'button')
                        .attr('data-toggle', 'dropdown')
                        .append(
                            $('<span>').addClass('caret')
                        ),
                    $('<ul>')
                        .addClass('dropdown-menu pull-right')
                        .append($liList)
                );

            let $item = $(html);

            $item.find('.item-content > input').after($dropdown);
            $item.find('.item-text').addClass('text-' + style);
            $item.addClass('link-group-item-with-columns');

            return $item.get(0).outerHTML;
        },

        fetch: function () {
            let data = Dep.prototype.fetch.call(this);

            data.style = {};

            (data.options || []).forEach(item => {
                data.style[item] = this.optionsStyleMap[item] || null;
            });

            return data;
        },
    });
});
PK]6r���6views/admin/field-manager/fields/not-actual-options.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/field-manager/fields/not-actual-options', ['views/fields/multi-enum'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            this.params.options = Espo.Utils.clone(this.model.get('options')) || [];

            this.listenTo(this.model, 'change:options', (m, v, o) => {
                this.params.options = Espo.Utils.clone(m.get('options')) || [];

                this.reRender();
            });
        },
    });
});
PK]hU�e�q�q!views/admin/field-manager/edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/field-manager/edit', ['view', 'model'], function (Dep, Model) {

    /**
     * @class
     * @name Class
     * @extends module:view
     * @memberOf module:views/admin/field-manager/edit
     */
    return Dep.extend(/** @lends module:views/admin/field-manager/edit.Class# */{

        template: 'admin/field-manager/edit',

        entityTypeWithTranslatedOptionsList: ['enum', 'multiEnum', 'array', 'phone'],

        paramWithTooltipList: [
            'audited',
            'required',
            'default',
            'min',
            'max',
            'maxLength',
            'after',
            'before',
            'readOnly',
        ],

        /**
         * @type {{
         *     forbidden?: boolean,
         *     internal?: boolean,
         *     onlyAdmin?: boolean,
         *     readOnly?: boolean,
         *     nonAdminReadOnly?: boolean,
         * }|{}}
         */
        globalRestriction: null,

        hasAnyGlobalRestriction: false,

        /**
         * @readonly
         */
        globalRestrictionTypeList: [
            'forbidden',
            'internal',
            'onlyAdmin',
            'readOnly',
            'nonAdminReadOnly',
        ],

        data: function () {
            return {
                scope: this.scope,
                field: this.field,
                defs: this.defs,
                paramList: this.paramList,
                type: this.type,
                fieldList: this.fieldList,
                isCustom: this.defs.isCustom,
                isNew: this.isNew,
                hasDynamicLogicPanel: this.hasDynamicLogicPanel,
                hasResetToDefault: !this.defs.isCustom && !this.entityTypeIsCustom && !this.isNew,
            };
        },

        events: {
            'click button[data-action="close"]': function () {
                this.actionClose();
            },
            'click button[data-action="save"]': function () {
                this.save();
            },
            'click button[data-action="resetToDefault"]': function () {
                this.resetToDefault();
            },
            'keydown.form': function (e) {
                let key = Espo.Utils.getKeyFromKeyEvent(e);

                if (key === 'Control+KeyS' || key === 'Control+Enter') {
                    this.save();

                    e.preventDefault();
                    e.stopPropagation();
                }
            },
        },

        setupFieldData: function (callback) {
            this.defs = {};
            this.fieldList = [];

            this.model = new Model();
            this.model.name = 'Admin';
            this.model.urlRoot = 'Admin/fieldManager/' + this.scope;

            this.model.defs = {
                fields: {
                    name: {required: true, maxLength: 50},
                    label: {required: true},
                    tooltipText: {},
                }
            };

            this.entityTypeIsCustom = !!this.getMetadata().get(['scopes', this.scope, 'isCustom']);

            this.globalRestriction = {};

            if (!this.isNew) {
                this.model.id = this.field;
                this.model.scope = this.scope;

                this.model.set('name', this.field);
                this.model.set(
                    'label',
                    this.getLanguage().translate(this.field, 'fields', this.scope)
                );

                if (this.getMetadata().get(['entityDefs', this.scope, 'fields', this.field, 'tooltip'])) {
                    this.model.set(
                        'tooltipText',
                        this.getLanguage().translate(this.field, 'tooltips', this.scope)
                    );
                }

                this.globalRestriction = this.getMetadata().get(['entityAcl', this.scope, 'fields', this.field]) || {};

                let globalRestrictions = this.globalRestrictionTypeList.filter(item => this.globalRestriction[item]);

                if (globalRestrictions.length) {
                    this.model.set('globalRestrictions', globalRestrictions);
                    this.hasAnyGlobalRestriction = true;
                }
            }
            else {
                this.model.scope = this.scope;
                this.model.set('type', this.type);
            }

            this.listenTo(this.model, 'change:readOnly', () => {
                this.readOnlyControl();
            });

            let hasRequired = false;

            this.getModelFactory().create(this.scope, model => {
                if (!this.isNew) {
                    this.type = model.getFieldType(this.field);
                }

                if (
                    this.getMetadata().get(['scopes', this.scope, 'hasPersonalData']) &&
                    this.getMetadata().get(['fields', this.type, 'personalData'])
                ) {
                    this.hasPersonalData = true;
                }

                this.hasInlineEditDisabled = !['foreign', 'autoincrement'].includes(this.type) &&
                    !this.getMetadata()
                        .get(['entityDefs', this.scope, 'fields', this.field,
                            'customizationInlineEditDisabledDisabled']);

                this.hasTooltipText = !this.getMetadata().get(['entityDefs', this.scope, 'fields', this.field,
                    'customizationTooltipTextDisabled']);

                new Promise(resolve => {
                    if (this.isNew) {
                        resolve();

                        return;
                    }

                    Espo.Ajax.getRequest('Admin/fieldManager/' + this.scope + '/' + this.field)
                        .then(data => {
                            this.defs = data;

                            resolve();
                        });
                })
                .then(() => {
                    let promiseList = [];
                    this.paramList = [];
                    let paramList = Espo.Utils.clone(this.getFieldManager().getParamList(this.type) || []);

                    if (!this.isNew) {
                        let fieldManagerAdditionalParamList =
                            this.getMetadata()
                                .get([
                                    'entityDefs', this.scope, 'fields',
                                    this.field, 'fieldManagerAdditionalParamList'
                                ]) || [];

                        fieldManagerAdditionalParamList.forEach((item) =>  {
                            paramList.push(item);
                        });
                    }

                    /** @var {string[]|null} */
                    let fieldManagerParamList = this.getMetadata()
                        .get(['entityDefs', this.scope, 'fields', this.field, 'fieldManagerParamList']);

                    paramList.forEach(o => {
                        let item = o.name;

                        if (fieldManagerParamList && fieldManagerParamList.indexOf(item) === -1) {
                            return;
                        }

                        if (
                            item === 'readOnly' &&
                            this.globalRestriction &&
                            this.globalRestriction.readOnly
                        ) {
                            return;
                        }

                        if (item === 'required') {
                            hasRequired = true;
                        }

                        let disableParamName = 'customization' + Espo.Utils.upperCaseFirst(item) + 'Disabled';

                        let isDisabled =
                            this.getMetadata()
                                .get(['entityDefs', this.scope, 'fields', this.field, disableParamName]);

                        if (isDisabled) {
                            return;
                        }

                        let viewParamName = 'customization' + Espo.Utils.upperCaseFirst(item) + 'View';

                        let view = this.getMetadata()
                            .get(['entityDefs', this.scope, 'fields', this.field, viewParamName]);

                        if (view) {
                            o.view = view;
                        }

                        this.paramList.push(o);
                    });

                    if (this.hasPersonalData) {
                        this.paramList.push({
                            name: 'isPersonalData',
                            type: 'bool',
                        });
                    }

                    if (
                        this.hasInlineEditDisabled &&
                        !this.globalRestriction.readOnly
                    ) {
                        this.paramList.push({
                            name: 'inlineEditDisabled',
                            type: 'bool',
                        });
                    }

                    if (this.hasTooltipText) {
                        this.paramList.push({
                            name: 'tooltipText',
                            type: 'text',
                            rowsMin: 1,
                            trim: true,
                        });
                    }

                    if (fieldManagerParamList) {
                        this.paramList = this.paramList
                            .filter(item => fieldManagerParamList.indexOf(item.name) !== -1);
                    }

                    this.paramList = this.paramList
                        .filter(item => {
                            return !(this.globalRestriction.readOnly && item.name === 'required');
                        });

                    let customizationDisabled = this.getMetadata()
                        .get(['entityDefs', this.scope, 'fields', this.field, 'customizationDisabled']);

                    if (
                        customizationDisabled ||
                        this.globalRestriction.forbidden
                    ) {
                        this.paramList = [];
                    }

                    if (this.hasAnyGlobalRestriction) {
                        this.paramList.push({
                            name: 'globalRestrictions',
                            type: 'array',
                            readOnly: true,
                            displayAsList: true,
                            translation: 'FieldManager.options.globalRestrictions',
                            options: this.globalRestrictionTypeList,
                        });
                    }

                    this.paramList.forEach(o => {
                        this.model.defs.fields[o.name] = o;
                    });

                    this.model.set(this.defs);

                    if (this.isNew) {
                        this.model.populateDefaults();
                    }

                    promiseList.push(
                        this.createFieldView('varchar', 'name', !this.isNew, {trim: true})
                    );

                    promiseList.push(
                        this.createFieldView('varchar', 'label', null, {trim: true})
                    );

                    this.hasDynamicLogicPanel = false;

                    promiseList.push(
                        this.setupDynamicLogicFields(hasRequired)
                    );

                    this.model.fetchedAttributes = this.model.getClonedAttributes();

                    this.paramList.forEach(o => {
                        if (o.hidden) {
                            return;
                        }

                        let options = {};

                        if (o.tooltip || ~this.paramWithTooltipList.indexOf(o.name)) {
                            options.tooltip = true;

                            let tooltip = o.name;

                            if (typeof o.tooltip === 'string') {
                                tooltip = o.tooltip;
                            }

                            options.tooltipText = this.translate(tooltip, 'tooltips', 'FieldManager');
                        }

                        if (o.readOnlyNotNew && !this.isNew) {
                            options.readOnly = true;
                        }

                        promiseList.push(
                            this.createFieldView(o.type, o.name, null, o, options)
                        );
                    });

                    Promise.all(promiseList).then(() => callback());
                });
            });

            this.listenTo(this.model, 'change', (m, o) => {
                if (!o.ui) {
                    return;
                }

                this.setIsChanged();
            });
        },

        setup: function () {
            this.scope = this.options.scope;
            this.field = this.options.field;
            this.type = this.options.type;

            this.isNew = !this.field;

            if (
                !this.getMetadata().get(['scopes', this.scope, 'customizable']) ||
                this.getMetadata().get(`scopes.${this.scope}.entityManager.fields`) === false ||
                (
                    this.field &&
                    this.getMetadata().get(`entityDefs.${this.scope}.fields.${this.field}.customizationDisabled`)
                )
            ) {
                Espo.Ui.notify(false);

                throw new Espo.Exceptions.NotFound("Entity type is not customizable.");
            }

            this.wait(true);

            this.setupFieldData(() => {
                this.wait(false);
            });
        },

        setupDynamicLogicFields: function (hasRequired) {
            let defs = this.getMetadata().get(['entityDefs', this.scope, 'fields', this.field]) || {};

            if (
                defs.disabled ||
                defs.dynamicLogicDisabled ||
                defs.layoutDetailDisabled ||
                defs.utility
            ) {
                return Promise.resolve();
            }

            let promiseList = [];

            if (!defs.dynamicLogicVisibleDisabled) {
                let isVisible = this.getMetadata()
                    .get(['clientDefs', this.scope, 'dynamicLogic', 'fields', this.field, 'visible']);

                this.model.set(
                    'dynamicLogicVisible',
                    isVisible
                );

                promiseList.push(
                    this.createFieldView(null, 'dynamicLogicVisible', null, {
                        view: 'views/admin/field-manager/fields/dynamic-logic-conditions',
                        scope: this.scope
                    })
                );

                this.hasDynamicLogicPanel = true;
            }

            let readOnly = this.getMetadata().get(['fields', this.type, 'readOnly']);

            if (!defs.dynamicLogicRequiredDisabled && !readOnly && hasRequired) {
                let dynamicLogicRequired = this.getMetadata()
                    .get(['clientDefs', this.scope, 'dynamicLogic', 'fields', this.field, 'required']);

                this.model.set('dynamicLogicRequired', dynamicLogicRequired);

                promiseList.push(
                    this.createFieldView(null, 'dynamicLogicRequired', null, {
                        view: 'views/admin/field-manager/fields/dynamic-logic-conditions',
                        scope: this.scope,
                    })
                );

                this.hasDynamicLogicPanel = true;
            }

            if (!defs.dynamicLogicReadOnlyDisabled && !readOnly) {
                let dynamicLogicReadOnly = this.getMetadata()
                    .get(['clientDefs', this.scope, 'dynamicLogic', 'fields', this.field, 'readOnly']);

                this.model.set('dynamicLogicReadOnly', dynamicLogicReadOnly);

                promiseList.push(
                    this.createFieldView(null, 'dynamicLogicReadOnly', null, {
                        view: 'views/admin/field-manager/fields/dynamic-logic-conditions',
                        scope: this.scope,
                    })
                );

                this.hasDynamicLogicPanel = true;
            }

            let typeDynamicLogicOptions = this.getMetadata().get(['fields', this.type, 'dynamicLogicOptions']);

            if (typeDynamicLogicOptions && !defs.dynamicLogicOptionsDisabled) {
                let dynamicLogicOptions =  this.getMetadata()
                    .get(['clientDefs', this.scope, 'dynamicLogic', 'options', this.field]);

                this.model.set('dynamicLogicOptions', dynamicLogicOptions);

                promiseList.push(
                    this.createFieldView(null, 'dynamicLogicOptions', null, {
                        view: 'views/admin/field-manager/fields/dynamic-logic-options',
                        scope: this.scope,
                    })
                );

                this.hasDynamicLogicPanel = true;
            }

            if (!defs.dynamicLogicInvalidDisabled && !readOnly) {
                let dynamicLogicInvalid = this.getMetadata()
                    .get(['clientDefs', this.scope, 'dynamicLogic', 'fields', this.field, 'invalid']);

                this.model.set('dynamicLogicInvalid', dynamicLogicInvalid);

                promiseList.push(
                    this.createFieldView(null, 'dynamicLogicInvalid', null, {
                        view: 'views/admin/field-manager/fields/dynamic-logic-conditions',
                        scope: this.scope,
                    })
                );

                this.hasDynamicLogicPanel = true;
            }

            return Promise.all(promiseList);
        },

        afterRender: function () {
            this.getView('name').on('change', () => {
                let name = this.model.get('name');

                let label = name;

                if (label.length) {
                     label = label.charAt(0).toUpperCase() + label.slice(1);
                }

                this.model.set('label', label);

                if (name) {
                    name = name
                        .replace(/-/g, '')
                        .replace(/_/g, '')
                        .replace(/[^\w\s]/gi, '')
                        .replace(/ (.)/g, (match, g) => {
                            return g.toUpperCase();
                        })
                        .replace(' ', '');

                    if (name.length) {
                         name = name.charAt(0).toLowerCase() + name.slice(1);
                    }
                }

                this.model.set('name', name);
            });
        },

        readOnlyControl: function () {
            if (this.model.get('readOnly')) {
                this.hideField('dynamicLogicReadOnly');
                this.hideField('dynamicLogicRequired');
                this.hideField('dynamicLogicOptions');
                this.hideField('dynamicLogicInvalid');
            }
            else {
                this.showField('dynamicLogicReadOnly');
                this.showField('dynamicLogicRequired');
                this.showField('dynamicLogicOptions');
                this.showField('dynamicLogicInvalid');
            }
        },

        hideField: function (name) {
            let f = () => {
                let view = this.getView(name);

                if (view) {
                    this.$el.find('.cell[data-name="'+name+'"]').addClass('hidden');

                    view.setDisabled();
                }
            };

            if (this.isRendered()) {
                f();
            }
            else {
                this.once('after:render', f);
            }
        },

        showField: function (name) {
            let f = () => {
                let view = this.getView(name);

                if (view) {
                    this.$el.find('.cell[data-name="'+name+'"]').removeClass('hidden');

                    view.setNotDisabled();
                }
            };

            if (this.isRendered()) {
                f();
            }
            else {
                this.once('after:render', f);
            }
        },

        createFieldView: function (type, name, readOnly, params, options, callback) {
            let viewName = (params || {}).view || this.getFieldManager().getViewName(type);

            let o = {
                model: this.model,
                selector: '.field[data-name="' + name + '"]',
                defs: {
                    name: name,
                    params: params
                },
                mode: readOnly ? 'detail' : 'edit',
                readOnly: readOnly,
                scope: this.scope,
                field: this.field,
            };

            _.extend(o, options || {});

            let promise = this.createView(name, viewName, o, callback);

            this.fieldList.push(name);

            return promise;
        },

        disableButtons: function () {
            this.$el.find('[data-action="save"]').attr('disabled', 'disabled').addClass('disabled');
            this.$el.find('[data-action="resetToDefault"]').attr('disabled', 'disabled').addClass('disabled');
        },

        enableButtons: function () {
            this.$el.find('[data-action="save"]').removeAttr('disabled').removeClass('disabled');
            this.$el.find('[data-action="resetToDefault"]').removeAttr('disabled').removeClass('disabled');
        },

        save: function () {
            this.disableButtons();

            this.fieldList.forEach(field => {
                let view = this.getView(field);

                if (!view.readOnly) {
                    view.fetchToModel();
                }
            });

            let notValid = false;

            this.fieldList.forEach((field) => {
                notValid = this.getView(field).validate() || notValid;
            });

            if (notValid) {
                this.notify('Not valid', 'error');
                this.enableButtons();

                return;
            }

            if (this.model.get('tooltipText') && this.model.get('tooltipText') !== '') {
                this.model.set('tooltip', true);
            }
            else {
                this.model.set('tooltip', false);
            }

            this.listenToOnce(this.model, 'sync', () => {
                Espo.Ui.notify(false);

                this.setIsNotChanged();
                this.enableButtons();
                this.updateLanguage();

                Promise.all([
                    this.getMetadata().loadSkipCache(),
                    this.getLanguage().loadSkipCache(),
                ])
                .then(() => this.trigger('after:save'));

                this.model.fetchedAttributes = this.model.getClonedAttributes();

                this.broadcastUpdate();
            });

            Espo.Ui.notify(this.translate('saving', 'messages'));

            if (this.isNew) {
                this.model
                    .save()
                    .catch(() => this.enableButtons());

                return;
            }

            let attributes = this.model.getClonedAttributes();

            if (this.model.fetchedAttributes.label === attributes.label) {
                delete attributes.label;
            }

            if (
                this.model.fetchedAttributes.tooltipText === attributes.tooltipText ||
                !this.model.fetchedAttributes.tooltipText && !attributes.tooltipText
            ) {
                delete attributes.tooltipText;
            }

            if ('translatedOptions' in attributes) {
                if (_.isEqual(this.model.fetchedAttributes.translatedOptions, attributes.translatedOptions)) {
                    delete attributes.translatedOptions;
                }
            }

            this.model
                .save(attributes, {patch: true})
                .catch(() => this.enableButtons());
        },

        updateLanguage: function () {
            let langData = this.getLanguage().data;

            if (this.scope in langData) {
                if (!('fields' in langData[this.scope])) {
                    langData[this.scope]['fields'] = {};
                }

                langData[this.scope]['fields'][this.model.get('name')] = this.model.get('label');

                if (!('tooltips' in langData[this.scope])) {
                    langData[this.scope]['tooltips'] = {};
                }

                langData[this.scope]['tooltips'][this.model.get('name')] = this.model.get('tooltipText');

                if (
                    this.getMetadata().get(['fields', this.model.get('type'), 'translatedOptions']) &&
                    this.model.get('translatedOptions')
                ) {
                    langData[this.scope].options = langData[this.scope].options || {};

                    langData[this.scope]['options'][this.model.get('name')] =
                        this.model.get('translatedOptions') || {};
                }
            }
        },

        resetToDefault: function () {
            this.confirm(this.translate('confirmation', 'messages'), () => {
                Espo.Ui.notify(this.translate('pleaseWait', 'messages'));

                Espo.Ajax.postRequest('FieldManager/action/resetToDefault', {
                    scope: this.scope,
                    name: this.field,
                }).then(() => {
                    Promise
                    .all([
                        this.getMetadata().loadSkipCache(),
                        this.getLanguage().loadSkipCache(),
                    ])
                    .then(() => {
                        this.setIsNotChanged();

                        this.setupFieldData(() => {
                            this.notify('Done', 'success');
                            this.reRender();
                            this.broadcastUpdate();
                        });
                    });
                });
            });
        },

        broadcastUpdate: function () {
            this.getHelper().broadcastChannel.postMessage('update:metadata');
            this.getHelper().broadcastChannel.postMessage('update:language');
            this.getHelper().broadcastChannel.postMessage('update:settings');
        },

        actionClose: function () {
            this.setIsNotChanged();

            this.getRouter().navigate('#Admin/fieldManager/scope=' + this.scope, {trigger: true});
        },

        setConfirmLeaveOut: function (value) {
            this.getRouter().confirmLeaveOut = value;
        },

        setIsChanged: function () {
            this.isChanged = true;
            this.setConfirmLeaveOut(true);
        },

        setIsNotChanged: function () {
            this.isChanged = false;
            this.setConfirmLeaveOut(false);
        },
    });
});
PK]h��H��-views/admin/field-manager/modals/add-field.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/field-manager/modals/add-field', ['views/modal'], function (Dep) {

    return Dep.extend({

        backdrop: true,

        template: 'admin/field-manager/modals/add-field',

        events: {
            'click a[data-action="addField"]': function (e) {
                let type = $(e.currentTarget).data('type');

                this.addField(type);
            },
            'keyup input[data-name="quick-search"]': function (e) {
                this.processQuickSearch(e.currentTarget.value);
            },
        },

        data: function () {
            return {
                typeList: this.typeList,
            };
        },

        setup: function () {
            this.headerText = this.translate('Add Field', 'labels', 'Admin');

            this.typeList = [];

            let fieldDefs = this.getMetadata().get('fields');

            Object.keys(this.getMetadata().get('fields')).forEach(type => {
                if (type in fieldDefs) {
                    if (!fieldDefs[type].notCreatable) {
                        this.typeList.push(type);
                    }
                }
            });

            this.typeDataList = this.typeList.map(type => {
                return {
                    type: type,
                    label: this.translate(type, 'fieldTypes', 'Admin'),
                };
            });

            this.typeList.sort((v1, v2) => {
                return this.translate(v1, 'fieldTypes', 'Admin')
                    .localeCompare(this.translate(v2, 'fieldTypes', 'Admin'));
            });
        },

        addField: function (type) {
            this.trigger('add-field', type);
            this.remove();
        },

        afterRender: function () {
            this.$noData = this.$el.find('.no-data');

            this.typeList.forEach(type => {
                let text = this.translate(type, 'fieldInfo', 'FieldManager');

                let $el = this.$el.find('a.info[data-name="'+type+'"]');

                if (text === type) {
                    $el.addClass('hidden');

                    return;
                }

                text = this.getHelper().transformMarkdownText(text, {linksInNewTab: true}).toString();

                Espo.Ui.popover($el, {
                    content: text,
                    placement: 'left',
                }, this);
            });

            setTimeout(() => this.$el.find('input[data-name="quick-search"]').focus(), 50);
        },

        processQuickSearch: function (text) {
            text = text.trim();

            let $noData = this.$noData;

            $noData.addClass('hidden');

            if (!text) {
                this.$el.find('ul .list-group-item').removeClass('hidden');

                return;
            }

            let matchedList = [];

            let lowerCaseText = text.toLowerCase();

            this.typeDataList.forEach(item => {
                let matched =
                    item.label.toLowerCase().indexOf(lowerCaseText) === 0 ||
                    item.type.toLowerCase().indexOf(lowerCaseText) === 0;

                if (matched) {
                    matchedList.push(item.type);
                }
            });

            if (matchedList.length === 0) {
                this.$el.find('ul .list-group-item').addClass('hidden');

                $noData.removeClass('hidden');

                return;
            }

            this.typeDataList.forEach(item => {
                let $row = this.$el.find(`ul .list-group-item[data-name="${item.type}"]`);

                if (!~matchedList.indexOf(item.type)) {
                    $row.addClass('hidden');

                    return;
                }

                $row.removeClass('hidden');
            });
        },
    });
});
PK].����!views/admin/field-manager/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/field-manager/list', ['view'], function (Dep) {

    return Dep.extend({

        template: 'admin/field-manager/list',

        data: function () {
            return {
                scope: this.scope,
                fieldDefsArray: this.fieldDefsArray,
                typeList: this.typeList,
                hasAddField: this.hasAddField,
            };
        },

        events: {
            'click [data-action="removeField"]': function (e) {
                var field = $(e.currentTarget).data('name');

                this.removeField(field);
            },
            'keyup input[data-name="quick-search"]': function (e) {
                this.processQuickSearch(e.currentTarget.value);
            },
        },

        setup: function () {
            this.scope = this.options.scope;

            this.isCustomizable =
                !!this.getMetadata().get(`scopes.${this.scope}.customizable`) &&
                this.getMetadata().get(`scopes.${this.scope}.entityManager.fields`) !== false;

            this.hasAddField = true;

            let entityManagerData = this.getMetadata().get(['scopes', this.scope, 'entityManager']) || {};

            if ('addField' in entityManagerData) {
                this.hasAddField = entityManagerData.addField;
            }

            this.wait(
                this.buildFieldDefs()
            );
        },

        afterRender: function () {
            this.$noData = this.$el.find('.no-data');

            this.$el.find('input[data-name="quick-search"]').focus();
        },

        buildFieldDefs: function () {
            return this.getModelFactory().create(this.scope).then(model => {
                this.fields = model.defs.fields;

                this.fieldList = Object.keys(this.fields).sort();
                this.fieldDefsArray = [];

                this.fieldList.forEach(field => {
                    let defs = this.fields[field];

                    this.fieldDefsArray.push({
                        name: field,
                        isCustom: defs.isCustom || false,
                        type: defs.type,
                        label: this.translate(field, 'fields', this.scope),
                        isEditable: !defs.customizationDisabled && this.isCustomizable,
                    });
                });
            });
        },

        removeField: function (field) {
            this.confirm(this.translate('confirmation', 'messages'), () => {
                Espo.Ui.notify(' ... ');

                Espo.Ajax.request('Admin/fieldManager/' + this.scope + '/' + field, 'delete').then(() => {
                    Espo.Ui.success(this.translate('Removed'));

                    this.$el.find('tr[data-name="'+field+'"]').remove();
                    var data = this.getMetadata().data;

                    delete data['entityDefs'][this.scope]['fields'][field];

                    this.getMetadata().loadSkipCache().then(() =>
                        this.buildFieldDefs()
                            .then(() => {
                                this.broadcastUpdate();

                                return this.reRender();
                            })
                            .then(() =>
                                Espo.Ui.success(this.translate('Removed'))
                            )
                    );
                });
            });
        },

        broadcastUpdate: function () {
            this.getHelper().broadcastChannel.postMessage('update:metadata');
            this.getHelper().broadcastChannel.postMessage('update:language');
        },

        processQuickSearch: function (text) {
            text = text.trim();

            let $noData = this.$noData;

            $noData.addClass('hidden');

            if (!text) {
                this.$el.find('table tr.field-row').removeClass('hidden');

                return;
            }

            let matchedList = [];

            let lowerCaseText = text.toLowerCase();

            this.fieldDefsArray.forEach(item => {
                let matched = false;

                if (
                    item.label.toLowerCase().indexOf(lowerCaseText) === 0 ||
                    item.name.toLowerCase().indexOf(lowerCaseText) === 0
                ) {
                    matched = true;
                }

                if (!matched) {
                    let wordList = item.label.split(' ')
                        .concat(
                            item.label.split(' ')
                        );

                    wordList.forEach((word) => {
                        if (word.toLowerCase().indexOf(lowerCaseText) === 0) {
                            matched = true;
                        }
                    });
                }

                if (matched) {
                    matchedList.push(item.name);
                }
            });

            if (matchedList.length === 0) {
                this.$el.find('table tr.field-row').addClass('hidden');

                $noData.removeClass('hidden');

                return;
            }

            this.fieldDefsArray
                .map(item => item.name)
                .forEach(field => {
                    let $row = this.$el.find(`table tr.field-row[data-name="${field}"]`);

                    if (!~matchedList.indexOf(field)) {
                        $row.addClass('hidden');

                        return;
                    }

                    $row.removeClass('hidden');
                });
        },
    });
});
PK]R"views/admin/field-manager/index.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/field-manager/index', ['view'], function (Dep) {

    return Dep.extend({

        template: 'admin/field-manager/index',

        scopeList: null,

        scope: null,

        type: null,

        data: function () {
            return {
                scopeList: this.scopeList,
                scope: this.scope,
            };
        },

        events: {
            'click #scopes-menu a.scope-link': function (e) {
                var scope = $(e.currentTarget).data('scope');

                this.openScope(scope);
            },

            'click #fields-content a.field-link': function (e) {
                e.preventDefault();

                var scope = $(e.currentTarget).data('scope');
                var field = $(e.currentTarget).data('field');

                this.openField(scope, field);
            },

            'click [data-action="addField"]': function () {
                this.createView('dialog', 'views/admin/field-manager/modals/add-field', {}, (view) => {
                    view.render();

                    this.listenToOnce(view, 'add-field', (type) => {
                        this.createField(this.scope, type);
                    });
                });
            },
        },

        setup: function () {
            this.scopeList = [];

            var scopesAll = Object.keys(this.getMetadata().get('scopes')).sort((v1, v2) => {
                return this.translate(v1, 'scopeNamesPlural').localeCompare(this.translate(v2, 'scopeNamesPlural'));
            });

            scopesAll.forEach((scope) => {
                if (this.getMetadata().get('scopes.' + scope + '.entity')) {
                    if (this.getMetadata().get('scopes.' + scope + '.customizable')) {
                        this.scopeList.push(scope);
                    }
                }
            });

            this.scope = this.options.scope || null;
            this.field = this.options.field || null;

            this.on('after:render', () => {
                if (!this.scope) {
                    this.renderDefaultPage();

                    return;
                }

                if (!this.field) {
                    this.openScope(this.scope);
                }
                else {
                    this.openField(this.scope, this.field);
                }
            });

            this.createView('header', 'views/admin/field-manager/header', {
                selector: '> .page-header',
                scope: this.scope,
                field: this.field,
            });
        },

        openScope: function (scope) {
            this.scope = scope;
            this.field = null;

            this.getView('header').setField(null);

            this.getRouter().navigate('#Admin/fieldManager/scope=' + scope, {trigger: false});

            Espo.Ui.notify(' ... ');

            this.createView('content', 'views/admin/field-manager/list', {
                fullSelector: '#fields-content',
                scope: scope,
            }, (view) => {
                view.render();

                Espo.Ui.notify(false);

                $(window).scrollTop(0);
            });
        },

        openField: function (scope, field) {
            this.scope = scope;
            this.field = field;

            this.getView('header').setField(field);

            this.getRouter()
                .navigate('#Admin/fieldManager/scope=' + scope + '&field=' + field, {trigger: false});

            Espo.Ui.notify(' ... ');

            this.createView('content', 'views/admin/field-manager/edit', {
                fullSelector: '#fields-content',
                scope: scope,
                field: field,
            }, (view) => {
                view.render();

                Espo.Ui.notify(false);

                $(window).scrollTop(0);

                this.listenTo(view, 'after:save', () => {
                    this.notify('Saved', 'success');
                });
            });
        },

        createField: function (scope, type) {
            this.scope = scope;
            this.type = type;

            this.getRouter()
                .navigate('#Admin/fieldManager/scope=' + scope + '&type=' + type + '&create=true', {trigger: false});

            Espo.Ui.notify(' ... ');

            this.createView('content', 'Admin.FieldManager.Edit', {
                fullSelector: '#fields-content',
                scope: scope,
                type: type,
            }, (view) => {
                view.render();

                Espo.Ui.notify(false);

                $(window).scrollTop(0);

                view.once('after:save', () => {
                    this.openScope(this.scope);

                    this.notify('Created', 'success');
                });
            });
        },

        renderDefaultPage: function () {
            $('#fields-content').html(this.translate('selectEntityType', 'messages', 'Admin'));
        },

        updatePageTitle: function () {
            this.setPageTitle(this.getLanguage().translate('Field Manager', 'labels', 'Admin'));
        },
    });
});
PK]��Hc�	�	views/admin/user-interface.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/user-interface', ['views/settings/record/edit'], function (Dep) {

    return Dep.extend({

        layoutName: 'userInterface',

        saveAndContinueEditingAction: false,

        setup: function () {
            Dep.prototype.setup.call(this);

            this.controlColorsField();
            this.listenTo(this.model, 'change:scopeColorsDisabled', this.controlColorsField, this);

            this.on('save', (initialAttributes) => {
                if (
                    this.model.get('theme') !== initialAttributes.theme ||
                    (this.model.get('themeParams').navbar || {}) !== (initialAttributes.themeParams).navbar
                ) {
                    this.setConfirmLeaveOut(false);

                    window.location.reload();
                }
            });
        },

        controlColorsField: function () {
            if (this.model.get('scopeColorsDisabled')) {
                this.hideField('tabColorsDisabled');
            } else {
                this.showField('tabColorsDisabled');
            }
        },
    });
});
PK]}�֍ � views/admin/index.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';

class AdminIndexView extends View {

    template = 'admin/index'

    events = {
        /** @this AdminIndexView */
        'click [data-action]': function (e) {
            Espo.Utils.handleAction(this, e.originalEvent, e.currentTarget);
        },
        /** @this AdminIndexView */
        'keyup input[data-name="quick-search"]': function (e) {
            this.processQuickSearch(e.currentTarget.value);
        },
    }

    data() {
        return {
            panelDataList: this.panelDataList,
            iframeUrl: this.iframeUrl,
            iframeHeight: this.getConfig().get('adminPanelIframeHeight') || 1330,
            iframeDisabled: this.getConfig().get('adminPanelIframeDisabled') || false,
        };
    }

    afterRender() {
        let $quickSearch = this.$el.find('input[data-name="quick-search"]');

        if (this.quickSearchText) {
            $quickSearch.val(this.quickSearchText);

            this.processQuickSearch(this.quickSearchText);
        }

        // noinspection JSUnresolvedReference
        $quickSearch.get(0).focus({preventScroll: true});
    }

    setup() {
        this.panelDataList = [];

        let panels = this.getMetadata().get('app.adminPanel') || {};

        for (let name in panels) {
            let panelItem = Espo.Utils.cloneDeep(panels[name]);

            panelItem.name = name;
            panelItem.itemList = panelItem.itemList || [];
            panelItem.label = this.translate(panelItem.label, 'labels', 'Admin');

            if (panelItem.itemList) {
                panelItem.itemList.forEach(item => {
                    item.label = this.translate(item.label, 'labels', 'Admin');

                    if (item.description) {
                        item.keywords = (this.getLanguage().get('Admin', 'keywords', item.description) || '')
                            .split(',');
                    } else {
                        item.keywords = [];
                    }
                });
            }

            // Legacy support.
            if (panelItem.items) {
                panelItem.items.forEach(item => {
                    item.label = this.translate(item.label, 'labels', 'Admin');
                    panelItem.itemList.push(item);

                    item.keywords = [];
                });
            }

            this.panelDataList.push(panelItem);
        }

        this.panelDataList.sort((v1, v2) => {
            if (!('order' in v1) && ('order' in v2)) {
                return 0;
            }

            if (!('order' in v2)) {
                return 0;
            }

            return v1.order - v2.order;
        });

        let iframeParams = [
            'version=' + encodeURIComponent(this.getConfig().get('version')),
            'css=' + encodeURIComponent(this.getConfig().get('siteUrl') +
                '/' + this.getThemeManager().getStylesheet())
        ];

        this.iframeUrl = this.getConfig().get('adminPanelIframeUrl') || 'https://s.espocrm.com/';

        if (~this.iframeUrl.indexOf('?')) {
            this.iframeUrl += '&' + iframeParams.join('&');
        } else {
            this.iframeUrl += '?' + iframeParams.join('&');
        }

        if (!this.getConfig().get('adminNotificationsDisabled')) {
            this.createView('notificationsPanel', 'views/admin/panels/notifications', {
                selector: '.notifications-panel-container'
            });
        }
    }

    processQuickSearch(text) {
        text = text.trim();

        this.quickSearchText = text;

        let $noData = this.$noData || this.$el.find('.no-data');

        $noData.addClass('hidden');

        if (!text) {
            this.$el.find('.admin-content-section').removeClass('hidden');
            this.$el.find('.admin-content-row').removeClass('hidden');

            return;
        }

        text = text.toLowerCase();

        this.$el.find('.admin-content-section').addClass('hidden');
        this.$el.find('.admin-content-row').addClass('hidden');

        let anythingMatched = false;

        this.panelDataList.forEach((panel, panelIndex) => {
            let panelMatched = false;
            let panelLabelMatched = false;

            if (panel.label && panel.label.toLowerCase().indexOf(text) === 0) {
                panelMatched = true;
                panelLabelMatched = true;
            }

            panel.itemList.forEach((row, rowIndex) => {
                if (!row.label) {
                    return;
                }

                let matched = false;

                if (panelLabelMatched) {
                    matched = true;
                }

                if (!matched) {
                    matched = row.label.toLowerCase().indexOf(text) === 0;
                }

                if (!matched) {
                    let wordList = row.label.split(' ');

                    wordList.forEach((word) => {
                        if (word.toLowerCase().indexOf(text) === 0) {
                            matched = true;
                        }
                    });

                    if (!matched) {
                        matched = ~row.keywords.indexOf(text);
                    }

                    if (!matched) {
                        if (text.length > 3) {
                            row.keywords.forEach((word) => {
                                if (word.indexOf(text) === 0) {
                                    matched = true;
                                }
                            });
                        }
                    }
                }

                if (matched) {
                    panelMatched = true;

                    this.$el.find(
                        '.admin-content-section[data-index="'+panelIndex.toString()+'"] '+
                        '.admin-content-row[data-index="'+rowIndex.toString()+'"]'
                    ).removeClass('hidden');

                    anythingMatched = true;
                }
            });

            if (panelMatched) {

                this.$el
                    .find('.admin-content-section[data-index="' + panelIndex.toString() + '"]')
                    .removeClass('hidden');

                anythingMatched = true;
            }
        });

        if (!anythingMatched) {
            $noData.removeClass('hidden');
        }
    }

    updatePageTitle() {
        this.setPageTitle(this.getLanguage().translate('Administration'));
    }

    // noinspection JSUnusedGlobalSymbols
    actionClearCache() {
        this.trigger('clear-cache');
    }

    // noinspection JSUnusedGlobalSymbols
    actionRebuild() {
        this.trigger('rebuild');
    }
}

export default AdminIndexView;
PK]ݑ�<UU%views/admin/entity-manager/formula.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';
import Model from 'model';
import EntityManagerEditFormulaRecordView from 'views/admin/entity-manager/record/edit-formula';
import _ from 'underscore';

class EntityManagerFormulaView extends View {

    template = 'admin/entity-manager/formula'

    /** @type {string} */
    scope

    attributes

    data() {
        return {
            scope: this.scope,
            type: this.type,
        };
    }

    setup() {
        this.addActionHandler('save', () => this.actionSave());
        this.addActionHandler('close', () => this.actionClose());
        this.addActionHandler('resetToDefault', () => this.actionResetToDefault());

        this.addHandler('keydown.form', '', 'onKeyDown');

        this.scope = this.options.scope;
        this.type = this.options.type;

        if (!this.scope || !this.type) {
            throw Error("No scope or type.");
        }


        if (
            !this.getMetadata().get(['scopes', this.scope, 'customizable']) ||
            this.getMetadata().get(`scopes.${this.scope}.entityManager.formula`) === false
        ) {
            throw new Espo.Exceptions.NotFound("Entity type is not customizable.");
        }

        if (!['beforeSaveCustomScript', 'beforeSaveApiScript'].includes(this.type)) {
            Espo.Ui.error('No allowed formula type.', true);

            throw new Espo.Exceptions.NotFound('No allowed formula type specified.');
        }

        this.model = new Model();
        this.model.name = 'EntityManager';

        this.wait(
            this.loadFormula().then(() => {
                this.recordView = new EntityManagerEditFormulaRecordView({
                    model: this.model,
                    targetEntityType: this.scope,
                    type: this.type,
                });

                this.assignView('record', this.recordView, '.record');
            })
        );

        this.listenTo(this.model, 'change', (m, o) => {
            if (!o.ui) {
                return;
            }

            this.setIsChanged();
        });
    }

    async loadFormula() {
        await Espo.Ajax
            .getRequest('Metadata/action/get', {key: 'formula.' + this.scope})
            .then(formulaData => {
                formulaData = formulaData || {};

                this.model.set(this.type, formulaData[this.type] || null);

                this.updateAttributes();
            });
    }

    afterRender() {
        this.$save = this.$el.find('[data-action="save"]');
    }

    disableButtons() {
        this.$save.addClass('disabled').attr('disabled', 'disabled');
    }

    enableButtons() {
        this.$save.removeClass('disabled').removeAttr('disabled');
    }

    updateAttributes() {
        this.attributes = Espo.Utils.clone(this.model.attributes);
    }

    actionSave() {
        let data = this.recordView.fetch();

        if (_.isEqual(data, this.attributes)) {
            Espo.Ui.warning(this.translate('notModified', 'messages'));

            return;
        }

        if (this.recordView.validate()) {
            return;
        }

        this.disableButtons();

        Espo.Ui.notify(' ... ');

        Espo.Ajax
            .postRequest('EntityManager/action/formula', {
                data: data,
                scope: this.scope,
            })
            .then(() => {
                Espo.Ui.success(this.translate('Saved'));

                this.enableButtons();
                this.setIsNotChanged();
                this.updateAttributes();
            })
            .catch(() => this.enableButtons());
    }

    actionClose() {
        this.setIsNotChanged();

        this.getRouter().navigate('#Admin/entityManager/scope=' + this.scope, {trigger: true});
    }

    async actionResetToDefault() {
        await this.confirm(this.translate('confirmation', 'messages'));

        this.disableButtons();
        Espo.Ui.notify(' ... ');

        try {
            await Espo.Ajax.postRequest('EntityManager/action/resetFormulaToDefault', {
                scope: this.scope,
                type: this.type,
            });
        }
        catch (e) {
            this.enableButtons();

            return;
        }

        await this.loadFormula();

        await this.recordView.reRender();

        this.enableButtons();
        this.setIsNotChanged();

        Espo.Ui.success(this.translate('Done'));
    }

    setConfirmLeaveOut(value) {
        this.getRouter().confirmLeaveOut = value;
    }

    setIsChanged() {
        this.isChanged = true;
        this.setConfirmLeaveOut(true);
    }

    setIsNotChanged() {
        this.isChanged = false;
        this.setConfirmLeaveOut(false);
    }

    updatePageTitle() {
        this.setPageTitle(this.getLanguage().translate('Formula', 'labels', 'EntityManager'));
    }

    /**
     * @param {KeyboardEvent} e
     */
    onKeyDown(e) {
        let key = Espo.Utils.getKeyFromKeyEvent(e);

        if (key === 'Control+KeyS' || key === 'Control+Enter') {
            this.actionSave();

            e.preventDefault();
            e.stopPropagation();
        }
    }
}

export default EntityManagerFormulaView;
PK]���

?views/admin/entity-manager/fields/duplicate-check-field-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import MultiEnumFieldView from 'views/fields/multi-enum';

class DuplicateFieldListCheckEntityManagerFieldView extends MultiEnumFieldView {

    fieldTypeList = [
        'varchar',
        'personName',
        'email',
        'phone',
        'url',
        'barcode',
    ]

    setupOptions() {
        let entityType = this.model.get('name');

        let options =
            this.getFieldManager()
                .getEntityTypeFieldList(entityType, {
                    typeList: this.fieldTypeList,
                    onlyAvailable: true,
                })
                .sort((a, b) => {
                    return this.getLanguage().translate(a, 'fields', this.entityType)
                        .localeCompare(
                            this.getLanguage().translate(b, 'fields', this.entityType)
                        );
                });

        this.translatedOptions = {};

        options.forEach(item => {
            this.translatedOptions[item] = this.translate(item, 'fields', entityType);
        })

        this.params.options = options;
    }
}

export default DuplicateFieldListCheckEntityManagerFieldView;
PK]�w3Ә	�	/views/admin/entity-manager/fields/icon-class.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/entity-manager/fields/icon-class', ['views/fields/base'], function (Dep) {

    return Dep.extend({

        editTemplate: 'admin/entity-manager/fields/icon-class/edit',

        setup: function () {
            Dep.prototype.setup.call(this);

            this.events['click [data-action="selectIcon"]'] = function () {
                this.selectIcon();
            };
        },

        selectIcon: function () {
            this.createView('dialog', 'views/admin/entity-manager/modals/select-icon', {}, view => {
                view.render();

                this.listenToOnce(view, 'select', value => {
                    if (value === '') {
                        value = null;
                    }

                    this.model.set(this.name, value);

                    view.close();
                });
            });
        },

        fetch: function () {
            let data = {};

            data[this.name] = this.model.get(this.name);

            return data;
        },
    });
});
PK]�����r�r"views/admin/entity-manager/edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';
import Model from 'model';

class EntityManagerEditView extends View {

    template = 'admin/entity-manager/edit'

    /**
     * @type {{
     *     string: {
     *         fieldDefs: Object.<string, *>,
     *         location?: string,
     *     }
     * }}
     */
    additionalParams
    defaultParamLocation = 'scopes'

    data() {
        return {
            isNew: this.isNew,
            scope: this.scope,
        };
    }

    setupData() {
        const scope = this.scope;
        const templateType = this.getMetadata().get(['scopes', scope, 'type']) || null;

        this.hasStreamField = true;

        if (scope) {
            this.hasStreamField = (
                this.getMetadata().get(['scopes', scope, 'customizable']) &&
                this.getMetadata().get(['scopes', scope, 'object'])
            ) || false;
        }

        if (scope === 'User') {
            this.hasStreamField = false;
        }

        this.hasColorField = !this.getConfig().get('scopeColorsDisabled');

        if (scope) {
            this.additionalParams = Espo.Utils.cloneDeep({
                ...this.getMetadata().get(['app', 'entityManagerParams', 'Global']),
                ...this.getMetadata().get(['app', 'entityManagerParams', '@' + (templateType || '_')]),
                ...this.getMetadata().get(['app', 'entityManagerParams', scope]),
            });

            this.model.set('name', scope);
            this.model.set('labelSingular', this.translate(scope, 'scopeNames'));
            this.model.set('labelPlural', this.translate(scope, 'scopeNamesPlural'));
            this.model.set('type', this.getMetadata().get('scopes.' + scope + '.type') || '');
            this.model.set('stream', this.getMetadata().get('scopes.' + scope + '.stream') || false);
            this.model.set('disabled', this.getMetadata().get('scopes.' + scope + '.disabled') || false);

            this.model.set('sortBy', this.getMetadata().get('entityDefs.' + scope + '.collection.orderBy'));
            this.model.set('sortDirection', this.getMetadata().get('entityDefs.' + scope + '.collection.order'));

            this.model.set('textFilterFields',
                this.getMetadata().get(['entityDefs', scope, 'collection', 'textFilterFields']) || ['name']
            );

            this.model.set('fullTextSearch',
                this.getMetadata().get(['entityDefs', scope, 'collection', 'fullTextSearch']) || false
            );

            this.model.set('countDisabled',
                this.getMetadata().get(['entityDefs', scope, 'collection', 'countDisabled']) || false
            );

            this.model.set('statusField', this.getMetadata().get('scopes.' + scope + '.statusField') || null);

            if (this.hasColorField) {
                this.model.set('color', this.getMetadata().get(['clientDefs', scope, 'color']) || null);
            }

            this.model.set('iconClass', this.getMetadata().get(['clientDefs', scope, 'iconClass']) || null);

            this.model.set(
                'kanbanViewMode',
                this.getMetadata().get(['clientDefs', scope, 'kanbanViewMode']) || false
            );

            this.model.set(
                'kanbanStatusIgnoreList',
                this.getMetadata().get(['scopes', scope, 'kanbanStatusIgnoreList']) || []
            );

            for (const param in this.additionalParams) {
                /** @type {{fieldDefs: Object, location?: string}} */
                const defs = this.additionalParams[param];
                const location = defs.location || this.defaultParamLocation;
                const defaultValue = defs.fieldDefs.type === 'bool' ? false : null;

                const value = this.getMetadata().get([location, scope, param]) || defaultValue;

                this.model.set(param, value);
            }
        }

        if (scope) {
            const fieldDefs = this.getMetadata().get('entityDefs.' + scope + '.fields') || {};

            this.orderableFieldList = Object.keys(fieldDefs)
                .filter(item => {
                    if (!this.getFieldManager().isEntityTypeFieldAvailable(scope, item)) {
                        return false;
                    }

                    if (fieldDefs[item].notStorable) {
                        return false;
                    }

                    return true;
                })
                .sort((v1, v2) => {
                    return this.translate(v1, 'fields', scope)
                        .localeCompare(this.translate(v2, 'fields', scope));
                });

            this.sortByTranslation = {};

            this.orderableFieldList.forEach(item => {
                this.sortByTranslation[item] = this.translate(item, 'fields', scope);
            });

            this.filtersOptionList = this.getTextFiltersOptionList(scope);

            this.textFilterFieldsTranslation = {};

            this.filtersOptionList.forEach(item => {
                if (~item.indexOf('.')) {
                    const link = item.split('.')[0];
                    const foreignField = item.split('.')[1];

                    const foreignEntityType = this.getMetadata()
                        .get(['entityDefs', scope, 'links', link, 'entity']);

                    this.textFilterFieldsTranslation[item] =
                        this.translate(link, 'links', scope) + '.' +
                        this.translate(foreignField, 'fields', foreignEntityType);

                    return;
                }

                this.textFilterFieldsTranslation[item] = this.translate(item, 'fields', scope);
            });

            this.enumFieldList = Object.keys(fieldDefs)
                .filter(item => {
                    if (fieldDefs[item].disabled) {
                        return;
                    }

                    if (fieldDefs[item].type === 'enum') {
                        return true;
                    }
                })
                .sort((v1, v2) => {
                    return this.translate(v1, 'fields', scope)
                        .localeCompare(this.translate(v2, 'fields', scope));
                });

            this.translatedStatusFields = {};

            this.enumFieldList.forEach(item => {
                this.translatedStatusFields[item] = this.translate(item, 'fields', scope);
            });

            this.enumFieldList.unshift('');

            this.translatedStatusFields[''] = '-' + this.translate('None') + '-';

            this.statusOptionList = [];
            this.translatedStatusOptions = {};
        }

        this.detailLayout = [
            {
                rows: [
                    [
                        {
                            name: 'name',
                        },
                        {
                            name: 'type',
                            options: {
                                tooltipText: this.translate('entityType', 'tooltips', 'EntityManager'),
                            }
                        },
                    ],
                    [
                        {
                            name: 'labelSingular',
                        },
                        {
                            name: 'labelPlural',
                        },
                    ],
                    [
                        {
                            name: 'iconClass',
                        },
                        {
                            name: 'color',
                        },
                    ],
                    [
                        {
                            name: 'disabled',
                        },
                        {
                            name: 'stream',
                        },
                    ],
                    [
                        {
                            name: 'sortBy',
                            options: {
                                translatedOptions: this.sortByTranslation,
                            },
                        },
                        {
                            name: 'sortDirection',
                        },
                    ],
                    [
                        {
                            name: 'textFilterFields',
                            options: {
                                translatedOptions: this.textFilterFieldsTranslation,
                            },
                        },
                        {
                            name: 'statusField',
                            options: {
                                translatedOptions: this.translatedStatusFields,
                            },
                        },
                    ],
                    [
                        {
                            name: 'fullTextSearch',
                        },
                        {
                            name: 'countDisabled',
                        },
                    ],
                    [
                        {
                            name: 'kanbanViewMode',
                        },
                        {
                            name: 'kanbanStatusIgnoreList',
                            options: {
                                translatedOptions: this.translatedStatusOptions,
                            },
                        },
                    ],
                ]
            },
        ];

        if (this.scope) {
            const rows1 = [];
            const rows2 = [];

            const paramList1 = Object.keys(this.additionalParams)
                .filter(item => !!this.getMetadata().get(['app', 'entityManagerParams', 'Global', item]));

            const paramList2 = Object.keys(this.additionalParams)
                .filter(item => !paramList1.includes(item));

            const add = function (rows, list) {
                list.forEach((param, i) => {
                    if (i % 2 === 0) {
                        rows.push([]);
                    }

                    const row = rows[rows.length - 1];

                    row.push({name: param});

                    if (
                        i === list.length - 1 &&
                        row.length === 1
                    ) {
                        row.push(false);
                    }
                });
            };

            add(rows1, paramList1);
            add(rows2, paramList2);

            if (rows1.length) {
                this.detailLayout.push({rows: rows1});
            }

            if (rows2.length) {
                this.detailLayout.push({rows: rows2});
            }
        }
    }

    setup() {
        const scope = this.scope = this.options.scope || false;
        this.isNew = !scope;

        this.model = new Model();
        this.model.name = 'EntityManager';

        if (!this.isNew) {
            this.isCustom = this.getMetadata().get(['scopes', scope, 'isCustom'])
        }

        if (
            this.scope &&
            (
                !this.getMetadata().get(`scopes.${scope}.customizable`) ||
                this.getMetadata().get(`scopes.${scope}.entityManager.edit`) === false
            )
        ) {
            throw new Espo.Exceptions.NotFound("The entity type is not customizable.");
        }

        this.setupData();
        this.setupDefs();

        this.model.fetchedAttributes = this.model.getClonedAttributes();

        this.createRecordView();
    }

    setupDefs() {
        const scope = this.scope;

        const defs = {
            fields: {
                type: {
                    type: 'enum',
                    required: true,
                    options: this.getMetadata().get('app.entityTemplateList') || ['Base'],
                    readOnly: scope !== false,
                    tooltip: true,
                },
                stream: {
                    type: 'bool',
                    required: true,
                    tooltip: true,
                },
                disabled: {
                    type: 'bool',
                    tooltip: true,
                },
                name: {
                    type: 'varchar',
                    required: true,
                    trim: true,
                    maxLength: 64,
                    readOnly: scope !== false,
                },
                labelSingular: {
                    type: 'varchar',
                    required: true,
                    trim: true,
                },
                labelPlural: {
                    type: 'varchar',
                    required: true,
                    trim: true,
                },
                color: {
                    type: 'varchar',
                    view: 'views/fields/colorpicker',
                },
                iconClass: {
                    type: 'varchar',
                    view: 'views/admin/entity-manager/fields/icon-class',
                },
                sortBy: {
                    type: 'enum',
                    options: this.orderableFieldList,
                },
                sortDirection: {
                    type: 'enum',
                    options: ['asc', 'desc'],
                },
                fullTextSearch: {
                    type: 'bool',
                    tooltip: true,
                },
                countDisabled: {
                    type: 'bool',
                    tooltip: true,
                },
                kanbanViewMode: {
                    type: 'bool',
                },
                textFilterFields: {
                    type: 'multiEnum',
                    options: this.filtersOptionList,
                    tooltip: true,
                },
                statusField: {
                    type: 'enum',
                    options: this.enumFieldList,
                    tooltip: true,
                },
                kanbanStatusIgnoreList: {
                    type: 'multiEnum',
                    options: this.statusOptionList,
                },
            },
        };

        if (this.getMetadata().get(['scopes', this.scope, 'statusFieldLocked'])) {
            defs.fields.statusField.readOnly = true;
        }

        for (const param in this.additionalParams) {
            defs.fields[param] = this.additionalParams[param].fieldDefs;
        }

        this.model.setDefs(defs);
    }

    createRecordView() {
        return this.createView('record', 'views/admin/entity-manager/record/edit', {
            selector: '.record',
            model: this.model,
            detailLayout: this.detailLayout,
            isNew: this.isNew,
            hasColorField: this.hasColorField,
            hasStreamField: this.hasStreamField,
            isCustom: this.isCustom,
            subjectEntityType: this.scope,
            shortcutKeysEnabled: true,
        }).then(view => {
            this.listenTo(view, 'save', () => this.actionSave());
            this.listenTo(view, 'cancel', () => this.actionCancel());
            this.listenTo(view, 'reset-to-default', () => this.actionResetToDefault());
        });
    }

    hideField(name) {
        this.getRecordView().hideField(name);
    }

    showField(name) {
        this.getRecordView().showField(name);
    }

    toPlural(string) {
        if (string.slice(-1) === 'y') {
            return string.substr(0, string.length - 1) + 'ies';
        }

        if (string.slice(-1) === 's') {
            return string + 'es';
        }

        return string + 's';
    }

    afterRender() {
        this.getFieldView('name').on('change', () => {
            let name = this.model.get('name');

            name = name.charAt(0).toUpperCase() + name.slice(1);

            this.model.set('labelSingular', name);
            this.model.set('labelPlural', this.toPlural(name)) ;

            if (name) {
                name = name
                    .replace(/-/g, ' ')
                    .replace(/_/g, ' ')
                    .replace(/[^\w\s]/gi, '')
                    .replace(/ (.)/g, (match, g) => {
                        return g.toUpperCase();
                    })
                    .replace(' ', '');

                if (name.length) {
                    name = name.charAt(0).toUpperCase() + name.slice(1);
                }
            }

            this.model.set('name', name);
        });
    }

    actionSave() {
        let fieldList = [
            'name',
            'type',
            'labelSingular',
            'labelPlural',
            'disabled',
            'statusField',
            'iconClass',
        ];

        if (this.hasStreamField) {
            fieldList.push('stream');
        }

        if (this.scope) {
            fieldList.push('sortBy');
            fieldList.push('sortDirection');
            fieldList.push('kanbanViewMode');
            fieldList.push('kanbanStatusIgnoreList');

            fieldList = fieldList.concat((Object.keys(this.additionalParams)));
        }

        if (this.hasColorField) {
            fieldList.push('color');
        }

        const fetchedAttributes = Espo.Utils.cloneDeep(this.model.fetchedAttributes) || {};

        let notValid = false;

        fieldList.forEach(item => {
            if (!this.getFieldView(item)) {
                return;
            }

            if (this.getFieldView(item).mode !== 'edit') {
                return;
            }

            this.getFieldView(item).fetchToModel();
        });

        fieldList.forEach(item => {
            if (!this.getFieldView(item)) {
                return;
            }

            if (this.getFieldView(item).mode !== 'edit') {
                return;
            }

            notValid = this.getFieldView(item).validate() || notValid;
        });

        if (notValid) {
            return;
        }

        this.disableButtons();

        let url = 'EntityManager/action/createEntity';

        if (this.scope) {
            url = 'EntityManager/action/updateEntity';
        }

        const name = this.model.get('name');

        const data = {
            name: name,
            labelSingular: this.model.get('labelSingular'),
            labelPlural: this.model.get('labelPlural'),
            type: this.model.get('type'),
            stream: this.model.get('stream'),
            disabled: this.model.get('disabled'),
            textFilterFields: this.model.get('textFilterFields'),
            fullTextSearch: this.model.get('fullTextSearch'),
            countDisabled: this.model.get('countDisabled'),
            statusField: this.model.get('statusField'),
            iconClass: this.model.get('iconClass'),
        };

        if (this.hasColorField) {
            data.color = this.model.get('color') || null;
        }

        if (data.statusField === '') {
            data.statusField = null;
        }

        if (this.scope) {
            data.sortBy = this.model.get('sortBy');
            data.sortDirection = this.model.get('sortDirection');
            data.kanbanViewMode = this.model.get('kanbanViewMode');
            data.kanbanStatusIgnoreList = this.model.get('kanbanStatusIgnoreList');

            for (const param in this.additionalParams) {
                const type = this.additionalParams[param].fieldDefs.type;

                this.getFieldManager().getAttributeList(type, param).forEach(attribute => {
                    data[attribute] = this.model.get(attribute);
                })
            }
        }

        if (!this.isNew) {
            if (this.model.fetchedAttributes.labelPlural === data.labelPlural) {
                delete data.labelPlural;
            }

            if (this.model.fetchedAttributes.labelSingular === data.labelSingular) {
                delete data.labelSingular;
            }
        }

        Espo.Ui.notify(this.translate('pleaseWait', 'messages'));

        Espo.Ajax.postRequest(url, data).then(() => {
            this.model.fetchedAttributes = this.model.getClonedAttributes();

            this.scope ?
                Espo.Ui.success(this.translate('Saved')) :
                Espo.Ui.success(this.translate('entityCreated', 'messages', 'EntityManager'))

            this.getMetadata().loadSkipCache()
            .then(
                () => Promise.all([
                    this.getConfig().load(),
                    this.getLanguage().loadSkipCache(),
                ])
            )
            .then(() => {
                const rebuildRequired =
                    data.fullTextSearch && !fetchedAttributes.fullTextSearch;

                this.broadcastUpdate();

                if (rebuildRequired) {
                    this.createView('dialog', 'views/modal', {
                        templateContent:
                            "{{complexText viewObject.options.msg}}" +
                            "{{complexText viewObject.options.msgRebuild}}",
                        headerText: this.translate('rebuildRequired', 'strings', 'Admin'),
                        backdrop: 'static',
                        msg: this.translate('rebuildRequired', 'messages', 'Admin'),
                        msgRebuild: '```php rebuild.php```',
                        buttonList: [
                            {
                                name: 'close',
                                label: this.translate('Close'),
                            },
                        ],
                    })
                    .then(view => view.render());
                }

                this.enableButtons();

                this.getRecordView().setIsNotChanged();

                if (this.isNew) {
                    this.getRouter().navigate('#Admin/entityManager/scope=' + name, {trigger: true});
                }
            });
        })
        .catch(() => {
            this.enableButtons();
        });
    }

    actionCancel() {
        this.getRecordView().setConfirmLeaveOut(false);

        if (!this.isNew) {
            this.getRouter().navigate('#Admin/entityManager/scope=' + this.scope, {trigger: true});

            return;
        }

        this.getRouter().navigate('#Admin/entityManager', {trigger: true});
    }

    actionResetToDefault() {
        this.confirm(this.translate('confirmation', 'messages'), () => {
            Espo.Ui.notify(this.translate('pleaseWait', 'messages'));

            this.disableButtons();

            Espo.Ajax.postRequest('EntityManager/action/resetToDefault', {scope: this.scope})
                .then(() => {
                    this.getMetadata()
                        .loadSkipCache()
                        .then(() => this.getLanguage().loadSkipCache())
                        .then(() => {
                            this.setupData();

                            this.model.fetchedAttributes = this.model.getClonedAttributes();

                            Espo.Ui.notify(this.translate('Done'), 'success');

                            this.enableButtons();
                            this.broadcastUpdate();

                            this.getRecordView().setIsNotChanged();
                        });
                });
        });
    }

    /**
     * @return {module:views/record/edit}
     */
    getRecordView() {
        return this.getView('record');
    }

    getTextFiltersOptionList(scope) {
        const fieldDefs = this.getMetadata().get(['entityDefs', scope, 'fields']) || {};

        const filtersOptionList = Object.keys(fieldDefs).filter(item => {
            const fieldType = fieldDefs[item].type;

            if (!this.getMetadata().get(['fields', fieldType, 'textFilter'])) {
                return false;
            }

            if (!this.getFieldManager().isEntityTypeFieldAvailable(scope, item)) {
                return false;
            }

            if (this.getMetadata().get(['entityDefs', scope, 'fields', item, 'textFilterDisabled'])) {
                return false;
            }

            return true;
        });

        filtersOptionList.unshift('id');

        const linkList = Object.keys(this.getMetadata().get(['entityDefs', scope, 'links']) || {});

        linkList.sort((v1, v2) => {
            return this.translate(v1, 'links', scope).localeCompare(this.translate(v2, 'links', scope));
        });

        linkList.forEach((link) => {
            const linkType = this.getMetadata().get(['entityDefs', scope, 'links', link, 'type']);

            if (linkType !== 'belongsTo') {
                return;
            }

            const foreignEntityType = this.getMetadata().get(['entityDefs', scope, 'links', link, 'entity']);

            if (!foreignEntityType) {
                return;
            }

            if (foreignEntityType === 'Attachment') {
                return;
            }

            const fields = this.getMetadata().get(['entityDefs', foreignEntityType, 'fields']) || {};

            const fieldList = Object.keys(fields);

            fieldList.sort((v1, v2) => {
                return this.translate(v1, 'fields', foreignEntityType)
                    .localeCompare(this.translate(v2, 'fields', foreignEntityType));
            });

            fieldList
                .filter(item => {
                    const fieldType = this.getMetadata()
                        .get(['entityDefs', foreignEntityType, 'fields', item, 'type']);

                    if (!this.getMetadata().get(['fields', fieldType, 'textFilter'])) {
                        return false;
                    }

                    if (!this.getMetadata().get(['fields', fieldType, 'textFilterForeign'])) {
                        return false;
                    }

                    if (!this.getFieldManager().isEntityTypeFieldAvailable(foreignEntityType, item)) {
                        return false;
                    }

                    if (
                        this.getMetadata()
                            .get(['entityDefs', foreignEntityType, 'fields', item, 'textFilterDisabled'])
                    ) {
                        return false;
                    }

                    if (
                        this.getMetadata()
                            .get(['entityDefs', foreignEntityType, 'fields', item, 'foreignAccessDisabled'])
                    ) {
                        return false;
                    }

                    return true;
                })
                .forEach((item) => {
                    filtersOptionList.push(link + '.' + item);
                });
        });

        return filtersOptionList;
    }

    getFieldView(name) {
        return this.getRecordView().getFieldView(name);
    }

    disableButtons() {
        this.getRecordView().disableActionItems();
    }

    enableButtons() {
        this.getRecordView().enableActionItems();
    }

    broadcastUpdate() {
        this.getHelper().broadcastChannel.postMessage('update:metadata');
        this.getHelper().broadcastChannel.postMessage('update:language');
        this.getHelper().broadcastChannel.postMessage('update:config');
    }
}

export default EntityManagerEditView;
PK]���M��1views/admin/entity-manager/record/edit-formula.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import BaseRecordView from 'views/record/base';

class EntityManagerEditFormulaRecordView extends BaseRecordView {

    template = 'admin/entity-manager/record/edit-formula'

    data() {
        return {
            field: this.field,
            fieldKey: this.field + 'Field',
        };
    }

    setup() {
        super.setup();

        this.field = this.options.type;

        let additionalFunctionDataList = null;

        if (this.options.type === 'beforeSaveApiScript') {
            additionalFunctionDataList = this.getRecordServiceFunctionDataList();
        }

        this.createField(
            this.field,
            'views/fields/formula',
            {
                targetEntityType: this.options.targetEntityType,
                height: 500,
            },
            'edit',
            false,
            {additionalFunctionDataList: additionalFunctionDataList}
        );
    }

    getRecordServiceFunctionDataList() {
        return [
            {
                name: 'recordService\\skipDuplicateCheck',
                insertText: 'recordService\\skipDuplicateCheck()',
                returnType: 'bool'
            },
            {
                name: 'recordService\\throwDuplicateConflict',
                insertText: 'recordService\\throwDuplicateConflict(RECORD_ID)',
            },
            {
                name: 'recordService\\throwBadRequest',
                insertText: 'recordService\\throwBadRequest(MESSAGE)',
            },
            {
                name: 'recordService\\throwForbidden',
                insertText: 'recordService\\throwForbidden(MESSAGE)',
            },
            {
                name: 'recordService\\throwConflict',
                insertText: 'recordService\\throwConflict(MESSAGE)',
            },
        ];
    }
}

export default EntityManagerEditFormulaRecordView;
PK]�����)views/admin/entity-manager/record/edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/entity-manager/record/edit', ['views/record/edit'], function (Dep) {

    return Dep.extend({

        bottomView: null,
        sideView: null,

        dropdownItemList: [],

        accessControlDisabled: true,
        saveAndContinueEditingAction: false,
        saveAndNewAction: false,

        shortcutKeys: {
            'Control+Enter': 'save',
            'Control+KeyS': 'save',
        },

        setup: function () {
            this.isCreate = this.options.isNew;

            this.scope = 'EntityManager';

            this.subjectEntityType = this.options.subjectEntityType;

            if (!this.isCreate) {
                this.buttonList = [
                    {
                        name: 'save',
                        style: 'danger',
                        label: 'Save',
                    },
                    {
                        name: 'cancel',
                        label: 'Cancel',
                    },
                ];
            }
            else {
                this.buttonList = [
                    {
                        name: 'save',
                        style: 'danger',
                        label: 'Create',
                    },
                    {
                        name: 'cancel',
                        label: 'Cancel',
                    },
                ];
            }

            if (!this.isCreate && !this.options.isCustom) {
                this.buttonList.push({
                    name: 'resetToDefault',
                    text: this.translate('Reset to Default', 'labels', 'Admin'),
                });
            }

            Dep.prototype.setup.call(this);

            if (this.isCreate) {
                this.hideField('sortBy');
                this.hideField('sortDirection');
                this.hideField('textFilterFields');
                this.hideField('statusField');
                this.hideField('fullTextSearch');
                this.hideField('countDisabled');
                this.hideField('kanbanViewMode');
                this.hideField('kanbanStatusIgnoreList');
                this.hideField('disabled');
            }

            if (!this.options.hasColorField) {
                this.hideField('color');
            }

            if (!this.options.hasStreamField) {
                this.hideField('stream');
            }

            if (!this.isCreate) {
                this.manageKanbanFields({});

                this.listenTo(this.model, 'change:statusField', (m, v, o) => {
                    this.manageKanbanFields(o);
                });

                this.manageKanbanViewModeField();

                this.listenTo(this.model, 'change:kanbanViewMode', () => {
                    this.manageKanbanViewModeField();
                });
            }
        },

        actionSave: function () {
            this.trigger('save');
        },

        actionCancel: function () {
            this.trigger('cancel');
        },

        actionResetToDefault: function () {
            this.trigger('reset-to-default');
        },

        manageKanbanViewModeField: function () {
            if (this.model.get('kanbanViewMode')) {
                this.showField('kanbanStatusIgnoreList');
            } else {
                this.hideField('kanbanStatusIgnoreList');
            }
        },

        manageKanbanFields: function (o) {
            if (o.ui) {
                this.model.set('kanbanStatusIgnoreList', []);
            }

            if (this.model.get('statusField')) {
                this.setKanbanStatusIgnoreListOptions();

                this.showField('kanbanViewMode');

                if (this.model.get('kanbanViewMode')) {
                    this.showField('kanbanStatusIgnoreList');
                } else {
                    this.hideField('kanbanStatusIgnoreList');
                }
            }
            else {
                this.hideField('kanbanViewMode');
                this.hideField('kanbanStatusIgnoreList');
            }
        },

        setKanbanStatusIgnoreListOptions: function () {
            let statusField = this.model.get('statusField');

            var optionList = this.getMetadata()
                .get(['entityDefs', this.subjectEntityType, 'fields', statusField, 'options']) || [];

            this.setFieldOptionList('kanbanStatusIgnoreList', optionList);

            let fieldView = this.getFieldView('kanbanStatusIgnoreList');

            if (!fieldView) {
                this.once('after:render', () => this.setKanbanStatusIgnoreListTranslation());

                return;
            }

            this.setKanbanStatusIgnoreListTranslation();
        },

        setKanbanStatusIgnoreListTranslation: function () {
            var fieldView = this.getFieldView('kanbanStatusIgnoreList');

            var statusField = this.model.get('statusField');

            var translation = this.getMetadata()
                .get(['entityDefs', this.subjectEntityType, 'fields', statusField, 'translation']) ||
                this.subjectEntityType + '.options.' + statusField;

            fieldView.params.translation = translation;
            fieldView.setupTranslation();
        },
    });
});
PK]/R��3views/admin/entity-manager/modals/select-formula.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/entity-manager/modals/select-formula', ['views/modal'], function (Dep) {

    /**
     * @class
     * @name Class
     * @extends module:views/modal
     * @memberOf module:views/admin/entity-manager/modals/select-formula
     */
    return Dep.extend(/** @lends module:views/admin/entity-manager/modals/select-formula.Class# */{

        // language=Handlebars
        templateContent: `
            <div class="panel no-side-margin">
                <table class="table table-bordered">
                    {{#each typeList}}
                    <tr>
                        <td style="width: 40%">
                            <a
                                class="btn btn-default btn-lg btn-full-wide"
                                href="#Admin/entityManager/formula&scope={{../scope}}&type={{this}}"
                            >
                            {{translate this category='fields' scope='EntityManager'}}
                            </a>
                        </td>
                        <td style="width: 60%">
                            <div class="complex-text">{{complexText (translate this category='messages' scope='EntityManager')}}
                        </td>
                    </tr>
                    {{/each}}
                </table>
            </div>
        `,

        backdrop: true,

        data: function () {
            return {
                typeList: this.typeList,
                scope: this.scope,
            };
        },

        setup: function () {
            this.scope = this.options.scope;

            this.typeList = [
                'beforeSaveCustomScript',
                'beforeSaveApiScript',
            ];

            this.headerText = this.translate('Formula', 'labels', 'EntityManager')
        },
    });
});
PK]d-[�+views/admin/entity-manager/modals/export.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ModalView from 'views/modal';
import Model from 'model';
import EditForModalRecordView from 'views/record/edit-for-modal';
import VarcharFieldView from 'views/fields/varchar';

class EntityManagerExportModalView extends ModalView {

    // language=Handlebars
    templateContent = `
        <div class="record-container no-side-margin">{{{record}}}</div>
    `

    setup() {
        this.headerText = this.translate('Export');

        this.buttonList = [
            {
                name: 'export',
                label: 'Export',
                style: 'danger',
                onClick: () => this.export(),
            },
            {
                name: 'cancel',
                label: 'Cancel',
            },
        ];

        let manifest = this.getConfig().get('customExportManifest') || {};

        this.model = new Model({
            name: manifest.name ?? null,
            module: manifest.module ?? null,
            version: manifest.version ?? '0.0.1',
            author: manifest.author ?? null,
            description: manifest.description ?? null,
        });

        this.recordView = new EditForModalRecordView({
            model: this.model,
            detailLayout: [
                {
                    rows: [
                        [
                            {
                                view: new VarcharFieldView({
                                    name: 'name',
                                    labelText: this.translate('name', 'fields'),
                                    params: {
                                        pattern: '$latinLettersDigitsWhitespace',
                                        required: true,
                                    },

                                }),
                            },
                            {
                                view: new VarcharFieldView({
                                    name: 'module',
                                    labelText: this.translate('module', 'fields', 'EntityManager'),
                                    params: {
                                        pattern: '[A-Z][a-z][A-Za-z]+',
                                        required: true,
                                    },
                                }),
                            },
                        ],
                        [
                            {
                                view: new VarcharFieldView({
                                    name: 'version',
                                    labelText: this.translate('version', 'fields', 'EntityManager'),
                                    params: {
                                        pattern: '[0-9]+\\.[0-9]+\\.[0-9]+',
                                        required: true,
                                    },
                                }),
                            },
                            false
                        ],
                        [
                            {
                                view: new VarcharFieldView({
                                    name: 'author',
                                    labelText: this.translate('author', 'fields', 'EntityManager'),
                                    params: {
                                        required: true,
                                    },
                                }),

                            },
                            {
                                view: new VarcharFieldView({
                                    name: 'description',
                                    labelText: this.translate('description', 'fields'),
                                    params: {},

                                }),
                            },
                        ],
                    ]
                }
            ]
        });

        this.assignView('record', this.recordView);
    }

    export() {
        const data = this.recordView.fetch();

        if (this.recordView.validate()) {
            return;
        }

        this.disableButton('export');

        Espo.Ui.notify(' ... ');

        Espo.Ajax
            .postRequest('EntityManager/action/exportCustom', data)
            .then(response => {
                this.close();

                this.getConfig().set('customExportManifest', data);

                Espo.Ui.success(this.translate('Done'));

                window.location = this.getBasePath() + '?entryPoint=download&id=' + response.id;
            })
            .catch(() => this.enableButton('create'));
    }
}

export default EntityManagerExportModalView;
PK]
S?�bb0views/admin/entity-manager/modals/select-icon.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/entity-manager/modals/select-icon', ['views/modal', 'model'], function (Dep, Model) {

    return Dep.extend({

        template: 'admin/entity-manager/modals/select-icon',

        buttonList: [
            {
                name: 'cancel',
                label: 'Cancel'
            }
        ],

        data: function () {
            return {
                iconDataList: this.getIconDataList()
            };
        },

        setup: function () {
            this.events['keyup input[data-name="quick-search"]'] = function (e) {
                this.processQuickSearch(e.currentTarget.value);
            };

            this.itemCache = {};

            this.iconList = ["fas fa-ad","fas fa-address-book","fas fa-address-card","fas fa-adjust","fas fa-air-freshener",
                "fas fa-align-center","fas fa-align-justify","fas fa-align-left","fas fa-align-right","fas fa-allergies",
                "fas fa-ambulance","fas fa-american-sign-language-interpreting","fas fa-anchor","fas fa-angle-double-down",
                "fas fa-angle-double-left","fas fa-angle-double-right","fas fa-angle-double-up","fas fa-angle-down",
                "fas fa-angle-left","fas fa-angle-right","fas fa-angle-up","fas fa-angry","fas fa-ankh","fas fa-apple-alt",
                "fas fa-archive","fas fa-archway","fas fa-arrow-alt-circle-down","fas fa-arrow-alt-circle-left",
                "fas fa-arrow-alt-circle-right","fas fa-arrow-alt-circle-up","fas fa-arrow-circle-down",
                "fas fa-arrow-circle-left","fas fa-arrow-circle-right","fas fa-arrow-circle-up",
                "fas fa-arrow-down","fas fa-arrow-left","fas fa-arrow-right","fas fa-arrow-up",
                "fas fa-arrows-alt","fas fa-arrows-alt-h","fas fa-arrows-alt-v","fas fa-assistive-listening-systems",
                "fas fa-asterisk","fas fa-at","fas fa-atlas","fas fa-atom","fas fa-audio-description",
                "fas fa-award","fas fa-baby","fas fa-baby-carriage","fas fa-backspace","fas fa-backward",
                "fas fa-balance-scale","fas fa-ban","fas fa-band-aid","fas fa-barcode","fas fa-bars",
                "fas fa-baseball-ball","fas fa-basketball-ball","fas fa-bath","fas fa-battery-empty",
                "fas fa-battery-full","fas fa-battery-half","fas fa-battery-quarter",
                "fas fa-battery-three-quarters","fas fa-bed","fas fa-beer","fas fa-bell","fas fa-bell-slash",
                "fas fa-bezier-curve","fas fa-bible","fas fa-bicycle","fas fa-binoculars","fas fa-biohazard",
                "fas fa-birthday-cake","fas fa-blender","fas fa-blender-phone","fas fa-blind","fas fa-blog",
                "fas fa-bold","fas fa-bolt","fas fa-bomb","fas fa-bone","fas fa-bong","fas fa-book",
                "fas fa-book-dead","fas fa-book-open","fas fa-book-reader","fas fa-bookmark",
                "fas fa-bowling-ball","fas fa-box","fas fa-box-open","fas fa-boxes","fas fa-braille","fas fa-brain",
                "fas fa-briefcase","fas fa-briefcase-medical","fas fa-broadcast-tower","fas fa-broom","fas fa-brush",
                "fas fa-bug","fas fa-building","fas fa-bullhorn","fas fa-bullseye","fas fa-burn","fas fa-bus",
                "fas fa-bus-alt","fas fa-business-time","fas fa-calculator","fas fa-calendar","fas fa-calendar-alt",
                "fas fa-calendar-check","fas fa-calendar-day","fas fa-calendar-minus","fas fa-calendar-plus",
                "fas fa-calendar-times","fas fa-calendar-week","fas fa-camera","fas fa-camera-retro",
                "fas fa-campground","fas fa-candy-cane","fas fa-cannabis","fas fa-capsules","fas fa-car",
                "fas fa-car-alt","fas fa-car-battery","fas fa-car-crash","fas fa-car-side","fas fa-caret-down",
                "fas fa-caret-left","fas fa-caret-right","fas fa-caret-square-down","fas fa-caret-square-left",
                "fas fa-caret-square-right","fas fa-caret-square-up","fas fa-caret-up","fas fa-carrot",
                "fas fa-cart-arrow-down","fas fa-cart-plus","fas fa-cash-register","fas fa-cat","fas fa-certificate",
                "fas fa-chair","fas fa-chalkboard","fas fa-chalkboard-teacher","fas fa-charging-station",
                "fas fa-chart-area","fas fa-chart-bar","fas fa-chart-line","fas fa-chart-pie","fas fa-check",
                "fas fa-check-circle","fas fa-check-double","fas fa-check-square","fas fa-chess","fas fa-chess-bishop",
                "fas fa-chess-board","fas fa-chess-king","fas fa-chess-knight","fas fa-chess-pawn",
                "fas fa-chess-queen","fas fa-chess-rook","fas fa-chevron-circle-down","fas fa-chevron-circle-left",
                "fas fa-chevron-circle-right","fas fa-chevron-circle-up","fas fa-chevron-down",
                "fas fa-chevron-left","fas fa-chevron-right","fas fa-chevron-up","fas fa-child","fas fa-church",
                "fas fa-circle","fas fa-circle-notch","fas fa-city","fas fa-clipboard","fas fa-clipboard-check",
                "fas fa-clipboard-list","fas fa-clock","fas fa-clone","fas fa-closed-captioning","fas fa-cloud",
                "fas fa-cloud-download-alt","fas fa-cloud-meatball","fas fa-cloud-moon","fas fa-cloud-moon-rain",
                "fas fa-cloud-rain","fas fa-cloud-showers-heavy","fas fa-cloud-sun","fas fa-cloud-sun-rain",
                "fas fa-cloud-upload-alt","fas fa-cocktail","fas fa-code","fas fa-code-branch","fas fa-coffee",
                "fas fa-cog","fas fa-cogs","fas fa-coins","fas fa-columns","fas fa-comment","fas fa-comment-alt",
                "fas fa-comment-dollar","fas fa-comment-dots","fas fa-comment-slash","fas fa-comments",
                "fas fa-comments-dollar","fas fa-compact-disc","fas fa-compass","fas fa-compress",
                "fas fa-compress-arrows-alt","fas fa-concierge-bell","fas fa-cookie","fas fa-cookie-bite",
                "fas fa-copy","fas fa-copyright","fas fa-couch","fas fa-credit-card","fas fa-crop",
                "fas fa-crop-alt","fas fa-cross","fas fa-crosshairs","fas fa-crow","fas fa-crown","fas fa-cube",
                "fas fa-cubes","fas fa-cut","fas fa-database","fas fa-deaf","fas fa-democrat","fas fa-desktop",
                "fas fa-dharmachakra","fas fa-diagnoses","fas fa-dice","fas fa-dice-d20","fas fa-dice-d6",
                "fas fa-dice-five","fas fa-dice-four","fas fa-dice-one","fas fa-dice-six","fas fa-dice-three","fas fa-dice-two","fas fa-digital-tachograph","fas fa-directions","fas fa-divide","fas fa-dizzy","fas fa-dna","fas fa-dog","fas fa-dollar-sign","fas fa-dolly","fas fa-dolly-flatbed","fas fa-donate","fas fa-door-closed","fas fa-door-open","fas fa-dot-circle","fas fa-dove","fas fa-download","fas fa-drafting-compass","fas fa-dragon","fas fa-draw-polygon","fas fa-drum","fas fa-drum-steelpan","fas fa-drumstick-bite","fas fa-dumbbell","fas fa-dumpster","fas fa-dumpster-fire","fas fa-dungeon","fas fa-edit","fas fa-eject","fas fa-ellipsis-h","fas fa-ellipsis-v","fas fa-envelope","fas fa-envelope-open","fas fa-envelope-open-text","fas fa-envelope-square","fas fa-equals","fas fa-eraser","fas fa-ethernet","fas fa-euro-sign","fas fa-exchange-alt","fas fa-exclamation","fas fa-exclamation-circle","fas fa-exclamation-triangle","fas fa-expand","fas fa-expand-arrows-alt","fas fa-external-link-alt","fas fa-external-link-square-alt","fas fa-eye","fas fa-eye-dropper","fas fa-eye-slash","fas fa-fast-backward","fas fa-fast-forward","fas fa-fax","fas fa-feather","fas fa-feather-alt","fas fa-female","fas fa-fighter-jet","fas fa-file","fas fa-file-alt","fas fa-file-archive","fas fa-file-audio","fas fa-file-code","fas fa-file-contract","fas fa-file-csv","fas fa-file-download","fas fa-file-excel","fas fa-file-export","fas fa-file-image","fas fa-file-import","fas fa-file-invoice","fas fa-file-invoice-dollar","fas fa-file-medical","fas fa-file-medical-alt","fas fa-file-pdf","fas fa-file-powerpoint","fas fa-file-prescription","fas fa-file-signature","fas fa-file-upload","fas fa-file-video","fas fa-file-word","fas fa-fill","fas fa-fill-drip","fas fa-film","fas fa-filter","fas fa-fingerprint","fas fa-fire","fas fa-fire-extinguisher","fas fa-first-aid","fas fa-fish","fas fa-fist-raised","fas fa-flag","fas fa-flag-checkered","fas fa-flag-usa","fas fa-flask","fas fa-flushed","fas fa-folder","fas fa-folder-minus","fas fa-folder-open","fas fa-folder-plus","fas fa-font","fas fa-football-ball","fas fa-forward","fas fa-frog","fas fa-frown","fas fa-frown-open","fas fa-funnel-dollar","fas fa-futbol","fas fa-gamepad","fas fa-gas-pump","fas fa-gavel","fas fa-gem","fas fa-genderless","fas fa-ghost","fas fa-gift","fas fa-gifts","fas fa-glass-cheers","fas fa-glass-martini","fas fa-glass-martini-alt","fas fa-glass-whiskey","fas fa-glasses","fas fa-globe","fas fa-globe-africa","fas fa-globe-americas","fas fa-globe-asia","fas fa-globe-europe","fas fa-golf-ball","fas fa-gopuram","fas fa-graduation-cap","fas fa-greater-than","fas fa-greater-than-equal","fas fa-grimace","fas fa-grin","fas fa-grin-alt","fas fa-grin-beam","fas fa-grin-beam-sweat","fas fa-grin-hearts","fas fa-grin-squint","fas fa-grin-squint-tears","fas fa-grin-stars","fas fa-grin-tears","fas fa-grin-tongue","fas fa-grin-tongue-squint","fas fa-grin-tongue-wink","fas fa-grin-wink","fas fa-grip-horizontal","fas fa-grip-lines","fas fa-grip-lines-vertical","fas fa-grip-vertical","fas fa-guitar","fas fa-h-square","fas fa-hammer","fas fa-hamsa","fas fa-hand-holding","fas fa-hand-holding-heart","fas fa-hand-holding-usd","fas fa-hand-lizard","fas fa-hand-paper","fas fa-hand-peace","fas fa-hand-point-down","fas fa-hand-point-left","fas fa-hand-point-right","fas fa-hand-point-up","fas fa-hand-pointer","fas fa-hand-rock","fas fa-hand-scissors","fas fa-hand-spock","fas fa-hands","fas fa-hands-helping","fas fa-handshake","fas fa-hanukiah","fas fa-hashtag","fas fa-hat-wizard","fas fa-haykal","fas fa-hdd","fas fa-heading","fas fa-headphones","fas fa-headphones-alt","fas fa-headset","fas fa-heart","fas fa-heart-broken","fas fa-heartbeat","fas fa-helicopter","fas fa-highlighter","fas fa-hiking","fas fa-hippo","fas fa-history","fas fa-hockey-puck","fas fa-holly-berry","fas fa-home","fas fa-horse","fas fa-horse-head","fas fa-hospital","fas fa-hospital-alt","fas fa-hospital-symbol","fas fa-hot-tub","fas fa-hotel","fas fa-hourglass","fas fa-hourglass-end","fas fa-hourglass-half","fas fa-hourglass-start","fas fa-house-damage","fas fa-hryvnia","fas fa-i-cursor","fas fa-icicles","fas fa-id-badge","fas fa-id-card","fas fa-id-card-alt","fas fa-igloo","fas fa-image","fas fa-images","fas fa-inbox","fas fa-indent","fas fa-industry","fas fa-infinity","fas fa-info","fas fa-info-circle","fas fa-italic","fas fa-jedi","fas fa-joint","fas fa-journal-whills","fas fa-kaaba","fas fa-key","fas fa-keyboard","fas fa-khanda","fas fa-kiss","fas fa-kiss-beam","fas fa-kiss-wink-heart","fas fa-kiwi-bird","fas fa-landmark","fas fa-language","fas fa-laptop","fas fa-laptop-code","fas fa-laugh","fas fa-laugh-beam","fas fa-laugh-squint","fas fa-laugh-wink","fas fa-layer-group","fas fa-leaf","fas fa-lemon","fas fa-less-than","fas fa-less-than-equal","fas fa-level-down-alt","fas fa-level-up-alt","fas fa-life-ring","fas fa-lightbulb","fas fa-link","fas fa-lira-sign","fas fa-list","fas fa-list-alt","fas fa-list-ol","fas fa-list-ul","fas fa-location-arrow","fas fa-lock","fas fa-lock-open","fas fa-long-arrow-alt-down","fas fa-long-arrow-alt-left","fas fa-long-arrow-alt-right","fas fa-long-arrow-alt-up","fas fa-low-vision","fas fa-luggage-cart","fas fa-magic","fas fa-magnet","fas fa-mail-bulk","fas fa-male","fas fa-map","fas fa-map-marked","fas fa-map-marked-alt","fas fa-map-marker","fas fa-map-marker-alt","fas fa-map-pin","fas fa-map-signs","fas fa-marker","fas fa-mars","fas fa-mars-double","fas fa-mars-stroke","fas fa-mars-stroke-h","fas fa-mars-stroke-v","fas fa-mask","fas fa-medal","fas fa-medkit","fas fa-meh","fas fa-meh-blank","fas fa-meh-rolling-eyes","fas fa-memory","fas fa-menorah","fas fa-mercury","fas fa-meteor","fas fa-microchip","fas fa-microphone","fas fa-microphone-alt","fas fa-microphone-alt-slash","fas fa-microphone-slash","fas fa-microscope","fas fa-minus","fas fa-minus-circle","fas fa-minus-square","fas fa-mitten","fas fa-mobile","fas fa-mobile-alt","fas fa-money-bill","fas fa-money-bill-alt","fas fa-money-bill-wave","fas fa-money-bill-wave-alt","fas fa-money-check","fas fa-money-check-alt","fas fa-monument","fas fa-moon","fas fa-mortar-pestle","fas fa-mosque","fas fa-motorcycle","fas fa-mountain","fas fa-mouse-pointer","fas fa-mug-hot","fas fa-music","fas fa-network-wired","fas fa-neuter","fas fa-newspaper","fas fa-not-equal","fas fa-notes-medical","fas fa-object-group","fas fa-object-ungroup","fas fa-oil-can","fas fa-om","fas fa-otter","fas fa-outdent","fas fa-paint-brush","fas fa-paint-roller","fas fa-palette","fas fa-pallet","fas fa-paper-plane","fas fa-paperclip","fas fa-parachute-box","fas fa-paragraph","fas fa-parking","fas fa-passport","fas fa-pastafarianism","fas fa-paste","fas fa-pause","fas fa-pause-circle","fas fa-paw","fas fa-peace","fas fa-pen","fas fa-pen-alt","fas fa-pen-fancy","fas fa-pen-nib","fas fa-pen-square","fas fa-pencil-alt","fas fa-pencil-ruler","fas fa-people-carry","fas fa-percent","fas fa-percentage","fas fa-person-booth","fas fa-phone","fas fa-phone-slash","fas fa-phone-square","fas fa-phone-volume","fas fa-piggy-bank","fas fa-pills","fas fa-place-of-worship","fas fa-plane","fas fa-plane-arrival","fas fa-plane-departure","fas fa-play","fas fa-play-circle","fas fa-plug","fas fa-plus","fas fa-plus-circle","fas fa-plus-square","fas fa-podcast","fas fa-poll","fas fa-poll-h","fas fa-poo","fas fa-poo-storm","fas fa-poop","fas fa-portrait","fas fa-pound-sign","fas fa-power-off","fas fa-pray","fas fa-praying-hands","fas fa-prescription","fas fa-prescription-bottle","fas fa-prescription-bottle-alt","fas fa-print","fas fa-procedures","fas fa-project-diagram","fas fa-puzzle-piece","fas fa-qrcode","fas fa-question","fas fa-question-circle","fas fa-quidditch","fas fa-quote-left","fas fa-quote-right","fas fa-quran","fas fa-radiation","fas fa-radiation-alt","fas fa-rainbow","fas fa-random","fas fa-receipt","fas fa-recycle","fas fa-redo","fas fa-redo-alt","fas fa-registered","fas fa-reply","fas fa-reply-all","fas fa-republican","fas fa-restroom","fas fa-retweet","fas fa-ribbon","fas fa-ring","fas fa-road","fas fa-robot","fas fa-rocket","fas fa-route","fas fa-rss","fas fa-rss-square","fas fa-ruble-sign","fas fa-ruler","fas fa-ruler-combined","fas fa-ruler-horizontal","fas fa-ruler-vertical","fas fa-running","fas fa-rupee-sign","fas fa-sad-cry","fas fa-sad-tear","fas fa-satellite","fas fa-satellite-dish","fas fa-save","fas fa-school","fas fa-screwdriver","fas fa-scroll","fas fa-sd-card","fas fa-search","fas fa-search-dollar","fas fa-search-location","fas fa-search-minus","fas fa-search-plus","fas fa-seedling","fas fa-server","fas fa-shapes","fas fa-share","fas fa-share-alt","fas fa-share-alt-square","fas fa-share-square","fas fa-shekel-sign","fas fa-shield-alt","fas fa-ship","fas fa-shipping-fast","fas fa-shoe-prints","fas fa-shopping-bag","fas fa-shopping-basket","fas fa-shopping-cart","fas fa-shower","fas fa-shuttle-van","fas fa-sign","fas fa-sign-in-alt","fas fa-sign-language","fas fa-sign-out-alt","fas fa-signal","fas fa-signature","fas fa-sim-card","fas fa-sitemap","fas fa-skating","fas fa-skiing","fas fa-skiing-nordic","fas fa-skull","fas fa-skull-crossbones","fas fa-slash","fas fa-sleigh","fas fa-sliders-h","fas fa-smile","fas fa-smile-beam","fas fa-smile-wink","fas fa-smog","fas fa-smoking","fas fa-smoking-ban","fas fa-sms","fas fa-snowboarding","fas fa-snowflake","fas fa-snowman","fas fa-snowplow","fas fa-socks","fas fa-solar-panel","fas fa-sort","fas fa-sort-alpha-down","fas fa-sort-alpha-up","fas fa-sort-amount-down","fas fa-sort-amount-up","fas fa-sort-down","fas fa-sort-numeric-down","fas fa-sort-numeric-up","fas fa-sort-up","fas fa-spa","fas fa-space-shuttle","fas fa-spider","fas fa-spinner","fas fa-splotch","fas fa-spray-can","fas fa-square","fas fa-square-full","fas fa-square-root-alt","fas fa-stamp","fas fa-star","fas fa-star-and-crescent","fas fa-star-half","fas fa-star-half-alt","fas fa-star-of-david","fas fa-star-of-life","fas fa-step-backward","fas fa-step-forward","fas fa-stethoscope","fas fa-sticky-note","fas fa-stop","fas fa-stop-circle","fas fa-stopwatch","fas fa-store","fas fa-store-alt","fas fa-stream","fas fa-street-view","fas fa-strikethrough","fas fa-stroopwafel","fas fa-subscript","fas fa-subway","fas fa-suitcase","fas fa-suitcase-rolling","fas fa-sun","fas fa-superscript","fas fa-surprise","fas fa-swatchbook","fas fa-swimmer","fas fa-swimming-pool","fas fa-synagogue","fas fa-sync","fas fa-sync-alt","fas fa-syringe","fas fa-table","fas fa-table-tennis","fas fa-tablet","fas fa-tablet-alt","fas fa-tablets","fas fa-tachometer-alt","fas fa-tag","fas fa-tags","fas fa-tape","fas fa-tasks","fas fa-taxi","fas fa-teeth","fas fa-teeth-open","fas fa-temperature-high","fas fa-temperature-low","fas fa-tenge","fas fa-terminal","fas fa-text-height","fas fa-text-width","fas fa-th","fas fa-th-large","fas fa-th-list","fas fa-theater-masks","fas fa-thermometer","fas fa-thermometer-empty","fas fa-thermometer-full","fas fa-thermometer-half","fas fa-thermometer-quarter","fas fa-thermometer-three-quarters","fas fa-thumbs-down","fas fa-thumbs-up","fas fa-thumbtack","fas fa-ticket-alt","fas fa-times","fas fa-times-circle","fas fa-tint","fas fa-tint-slash","fas fa-tired","fas fa-toggle-off","fas fa-toggle-on","fas fa-toilet","fas fa-toilet-paper","fas fa-toolbox","fas fa-tools","fas fa-tooth","fas fa-torah","fas fa-torii-gate","fas fa-tractor","fas fa-trademark","fas fa-traffic-light","fas fa-train","fas fa-tram","fas fa-transgender","fas fa-transgender-alt","fas fa-trash","fas fa-trash-alt","fas fa-tree","fas fa-trophy","fas fa-truck","fas fa-truck-loading","fas fa-truck-monster","fas fa-truck-moving","fas fa-truck-pickup","fas fa-tshirt","fas fa-tty","fas fa-tv","fas fa-umbrella","fas fa-umbrella-beach","fas fa-underline","fas fa-undo","fas fa-undo-alt","fas fa-universal-access","fas fa-university","fas fa-unlink","fas fa-unlock","fas fa-unlock-alt","fas fa-upload","fas fa-user","fas fa-user-alt","fas fa-user-alt-slash","fas fa-user-astronaut","fas fa-user-check","fas fa-user-circle","fas fa-user-clock","fas fa-user-cog","fas fa-user-edit","fas fa-user-friends","fas fa-user-graduate","fas fa-user-injured","fas fa-user-lock","fas fa-user-md","fas fa-user-minus","fas fa-user-ninja","fas fa-user-plus","fas fa-user-secret","fas fa-user-shield","fas fa-user-slash","fas fa-user-tag","fas fa-user-tie","fas fa-user-times","fas fa-users","fas fa-users-cog","fas fa-utensil-spoon","fas fa-utensils","fas fa-vector-square","fas fa-venus","fas fa-venus-double","fas fa-venus-mars","fas fa-vial","fas fa-vials","fas fa-video","fas fa-video-slash","fas fa-vihara","fas fa-volleyball-ball","fas fa-volume-down","fas fa-volume-mute","fas fa-volume-off","fas fa-volume-up","fas fa-vote-yea","fas fa-vr-cardboard","fas fa-walking","fas fa-wallet","fas fa-warehouse","fas fa-water","fas fa-weight","fas fa-weight-hanging","fas fa-wheelchair","fas fa-wifi","fas fa-wind","fas fa-window-close","fas fa-window-maximize","fas fa-window-minimize","fas fa-window-restore","fas fa-wine-bottle","fas fa-wine-glass","fas fa-wine-glass-alt","fas fa-won-sign","fas fa-wrench","fas fa-x-ray","fas fa-yen-sign","fas fa-yin-yang","far fa-address-book","far fa-address-card","far fa-angry","far fa-arrow-alt-circle-down","far fa-arrow-alt-circle-left","far fa-arrow-alt-circle-right","far fa-arrow-alt-circle-up","far fa-bell","far fa-bell-slash","far fa-bookmark","far fa-building","far fa-calendar","far fa-calendar-alt","far fa-calendar-check","far fa-calendar-minus","far fa-calendar-plus","far fa-calendar-times","far fa-caret-square-down","far fa-caret-square-left","far fa-caret-square-right","far fa-caret-square-up","far fa-chart-bar","far fa-check-circle","far fa-check-square","far fa-circle","far fa-clipboard","far fa-clock","far fa-clone","far fa-closed-captioning","far fa-comment","far fa-comment-alt","far fa-comment-dots","far fa-comments","far fa-compass","far fa-copy","far fa-copyright","far fa-credit-card","far fa-dizzy","far fa-dot-circle","far fa-edit","far fa-envelope","far fa-envelope-open","far fa-eye","far fa-eye-slash","far fa-file","far fa-file-alt","far fa-file-archive","far fa-file-audio","far fa-file-code","far fa-file-excel","far fa-file-image","far fa-file-pdf","far fa-file-powerpoint","far fa-file-video","far fa-file-word","far fa-flag","far fa-flushed","far fa-folder","far fa-folder-open","far fa-frown","far fa-frown-open","far fa-futbol","far fa-gem","far fa-grimace","far fa-grin","far fa-grin-alt","far fa-grin-beam","far fa-grin-beam-sweat","far fa-grin-hearts","far fa-grin-squint","far fa-grin-squint-tears","far fa-grin-stars","far fa-grin-tears","far fa-grin-tongue","far fa-grin-tongue-squint","far fa-grin-tongue-wink","far fa-grin-wink","far fa-hand-lizard","far fa-hand-paper","far fa-hand-peace","far fa-hand-point-down","far fa-hand-point-left","far fa-hand-point-right","far fa-hand-point-up","far fa-hand-pointer","far fa-hand-rock","far fa-hand-scissors","far fa-hand-spock","far fa-handshake","far fa-hdd","far fa-heart","far fa-hospital","far fa-hourglass","far fa-id-badge","far fa-id-card","far fa-image","far fa-images","far fa-keyboard","far fa-kiss","far fa-kiss-beam","far fa-kiss-wink-heart","far fa-laugh","far fa-laugh-beam","far fa-laugh-squint","far fa-laugh-wink","far fa-lemon","far fa-life-ring","far fa-lightbulb","far fa-list-alt","far fa-map","far fa-meh","far fa-meh-blank","far fa-meh-rolling-eyes","far fa-minus-square","far fa-money-bill-alt","far fa-moon","far fa-newspaper","far fa-object-group","far fa-object-ungroup","far fa-paper-plane","far fa-pause-circle","far fa-play-circle","far fa-plus-square","far fa-question-circle","far fa-registered","far fa-sad-cry","far fa-sad-tear","far fa-save","far fa-share-square","far fa-smile","far fa-smile-beam","far fa-smile-wink","far fa-snowflake","far fa-square","far fa-star","far fa-star-half","far fa-sticky-note","far fa-stop-circle","far fa-sun","far fa-surprise","far fa-thumbs-down","far fa-thumbs-up","far fa-times-circle","far fa-tired","far fa-trash-alt","far fa-user","far fa-user-circle","far fa-window-close","far fa-window-maximize","far fa-window-minimize","far fa-window-restore"];
        },

        actionSelect: function (data) {
            this.trigger('select', data.value);
        },

        getIconDataList: function () {
            var rowList = [];

            this.iconList.forEach(function (item, i) {
                if (i % 12 === 0) {
                    rowList.push([]);
                }
                rowList[rowList.length - 1].push(item);
            }, this);

            return rowList;
        },

        processQuickSearch: function (filter) {
            if (!filter) {
                this.$el.find('.icon-container').removeClass('hidden');

                return;
            }

            var $container = this.$el.find('.icons');

            this.iconList.forEach(function (item) {
                var $icon = this.itemCache[item];

                if (!$icon) {
                    $icon = $container.find('> .icon-container[data-name="' + item + '"]');

                    this.itemCache[item] = $icon;
                }

                if (~item.indexOf(filter)) {
                    $icon.removeClass('hidden');

                    return;
                }

                $icon.addClass('hidden');
            }.bind(this));
        },

    });
});
PK]��oN#views/admin/entity-manager/scope.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/admin/entity-manager/scope', ['view'], function (Dep) {

    return Dep.extend({

        template: 'admin/entity-manager/scope',

        scope: null,

        data: function () {
            return {
                scope: this.scope,
                isEditable: this.isEditable,
                isRemovable: this.isRemovable,
                isCustomizable: this.isCustomizable,
                type: this.type,
                hasLayouts: this.hasLayouts,
                label: this.label,
                hasFormula: this.hasFormula,
                hasFields: this.hasFields,
                hasRelationships: this.hasRelationships,
            };
        },

        events: {
            'click [data-action="editEntity"]': function () {
                this.getRouter().navigate('#Admin/entityManager/edit&scope=' + this.scope, {trigger: true});
            },
            'click [data-action="removeEntity"]': function () {
                this.removeEntity();
            },
            'click [data-action="editFormula"]': function () {
                this.editFormula();
            },
        },

        setup: function () {
            this.scope = this.options.scope;

            this.setupScopeData();
        },

        setupScopeData: function () {
            let scopeData = this.getMetadata().get(['scopes', this.scope]);
            let entityManagerData = this.getMetadata().get(['scopes', this.scope, 'entityManager']) || {};

            if (!scopeData) {
                throw new Espo.Exceptions.NotFound();
            }

            this.isRemovable = !!scopeData.isCustom;

            if (scopeData.isNotRemovable) {
                this.isRemovable = false;
            }

            this.isCustomizable = !!scopeData.customizable;
            this.type = scopeData.type;
            this.isEditable = true;
            this.hasLayouts = scopeData.layouts;
            this.hasFormula = this.isCustomizable;
            this.hasFields = this.isCustomizable;
            this.hasRelationships = this.isCustomizable;

            if (!scopeData.customizable) {
                this.isEditable = false;
            }

            if ('edit' in entityManagerData) {
                this.isEditable = entityManagerData.edit;
            }

            if ('layouts' in entityManagerData) {
                this.hasLayouts = entityManagerData.layouts;
            }

            if ('formula' in entityManagerData) {
                this.hasFormula = entityManagerData.formula;
            }

            if ('fields' in entityManagerData) {
                this.hasFields = entityManagerData.fields;
            }

            if ('relationships' in entityManagerData) {
                this.hasRelationships = entityManagerData.relationships;
            }

            this.label = this.getLanguage().translate(this.scope, 'scopeNames');
        },

        editFormula: function () {
            Espo.Ui.notify(' ... ');

            Espo.loader.requirePromise('views/admin/entity-manager/modals/select-formula')
                .then(View => {
                    /** @type {module:views/modal} */
                    let view = new View({
                        scope: this.scope,
                    });

                    this.assignView('dialog', view).then(() => {
                        Espo.Ui.notify(false);

                        view.render();
                    });
                });
        },

        removeEntity: function () {
            var scope = this.scope;

            this.confirm(this.translate('confirmRemove', 'messages', 'EntityManager'), () => {
                Espo.Ui.notify(
                    this.translate('pleaseWait', 'messages')
                );

                this.disableButtons();

                Espo.Ajax.postRequest('EntityManager/action/removeEntity', {
                    name: scope,
                })
                .then(() => {
                    this.getMetadata()
                        .loadSkipCache()
                        .then(() => {
                            this.getConfig().load().then(() => {
                                Espo.Ui.notify(false);

                                this.broadcastUpdate();

                                this.getRouter().navigate('#Admin/entityManager', {trigger: true});
                            });
                        });
                })
                .catch(() => this.enableButtons());
            });
        },

        updatePageTitle: function () {
            this.setPageTitle(
                this.getLanguage().translate('Entity Manager', 'labels', 'Admin')
            );
        },

        disableButtons: function () {
            this.$el.find('.btn.action').addClass('disabled').attr('disabled', 'disabled');
            this.$el.find('.item-dropdown-button').addClass('disabled').attr('disabled', 'disabled');
        },

        enableButtons: function () {
            this.$el.find('.btn.action').removeClass('disabled').removeAttr('disabled');
            this.$el.find('.item-dropdown-button"]').removeClass('disabled').removeAttr('disabled');
        },

        broadcastUpdate: function () {
            this.getHelper().broadcastChannel.postMessage('update:metadata');
            this.getHelper().broadcastChannel.postMessage('update:settings');
        },
    });
});
PK]���#views/admin/entity-manager/index.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';
import EntityManagerExportModalView from 'views/admin/entity-manager/modals/export';

class EntityManagerIndexView extends View {

    template = 'admin/entity-manager/index'
    scopeDataList = null
    scope = null

    data() {
        return {
            scopeDataList: this.scopeDataList,
        };
    }

    events = {
        /** @this EntityManagerIndexView */
        'click button[data-action="createEntity"]': function () {
            this.getRouter().navigate('#Admin/entityManager/create&', {trigger: true});
        },
        /** @this EntityManagerIndexView */
        'keyup input[data-name="quick-search"]': function (e) {
            this.processQuickSearch(e.currentTarget.value);
        },
    }

    setupScopeData() {
        this.scopeDataList = [];

        let scopeList = Object.keys(this.getMetadata().get('scopes'))
            .sort((v1, v2) => {
                return v1.localeCompare(v2);
            });

        let scopeListSorted = [];

        scopeList.forEach(scope => {
            var d = this.getMetadata().get('scopes.' + scope);

            if (d.entity && d.customizable) {
                scopeListSorted.push(scope);
            }
        });

        scopeList.forEach(scope => {
            var d = this.getMetadata().get('scopes.' + scope);

            if (d.entity && !d.customizable) {
                scopeListSorted.push(scope);
            }
        });

        scopeList = scopeListSorted;

        scopeList.forEach(scope => {
            let d = /** @type {Object.<string, *>} */this.getMetadata().get('scopes.' + scope);

            let isRemovable = !!d.isCustom;

            if (d.isNotRemovable) {
                isRemovable = false;
            }

            let hasView = d.customizable;

            this.scopeDataList.push({
                name: scope,
                isCustom: d.isCustom,
                isRemovable: isRemovable,
                hasView: hasView,
                type: d.type,
                label: this.getLanguage().translate(scope, 'scopeNames'),
                layouts: d.layouts,
            });
        });
    }

    setup() {
        this.setupScopeData();

        this.addActionHandler('export', () => this.actionExport());
    }

    afterRender() {
        this.$noData = this.$el.find('.no-data');

        this.$el.find('input[data-name="quick-search"]').focus();
    }

    updatePageTitle() {
        this.setPageTitle(this.getLanguage().translate('Entity Manager', 'labels', 'Admin'));
    }

    processQuickSearch(text) {
        text = text.trim();

        let $noData = this.$noData;

        $noData.addClass('hidden');

        if (!text) {
            this.$el.find('table tr.scope-row').removeClass('hidden');

            return;
        }

        let matchedList = [];

        let lowerCaseText = text.toLowerCase();

        this.scopeDataList.forEach(item => {
            let matched = false;

            if (
                item.label.toLowerCase().indexOf(lowerCaseText) === 0 ||
                item.name.toLowerCase().indexOf(lowerCaseText) === 0
            ) {
                matched = true;
            }

            if (!matched) {
                let wordList = item.label.split(' ')
                    .concat(
                        item.label.split(' ')
                    );

                wordList.forEach((word) => {
                    if (word.toLowerCase().indexOf(lowerCaseText) === 0) {
                        matched = true;
                    }
                });
            }

            if (matched) {
                matchedList.push(item.name);
            }
        });

        if (matchedList.length === 0) {
            this.$el.find('table tr.scope-row').addClass('hidden');

            $noData.removeClass('hidden');

            return;
        }

        this.scopeDataList
            .map(item => item.name)
            .forEach(scope => {
                if (!~matchedList.indexOf(scope)) {
                    this.$el.find('table tr.scope-row[data-scope="'+scope+'"]').addClass('hidden');

                    return;
                }

                this.$el.find('table tr.scope-row[data-scope="'+scope+'"]').removeClass('hidden');
            });
    }

    actionExport() {
        const view = new EntityManagerExportModalView();

        this.assignView('dialog', view)
            .then(() => {
                view.render();
            })
    }
}

export default EntityManagerIndexView;
PK]t�
��views/header.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/header */

import View from 'view';

class HeaderView extends View {

    template = 'header'

    data() {
        let data = {};

        if ('getHeader' in this.getParentMainView()) {
            data.header = this.getParentMainView().getHeader();
        }

        data.scope = this.scope || this.getParentMainView().scope;
        data.items = this.getItems();

        let dropdown = (data.items || {}).dropdown || [];

        data.hasVisibleDropdownItems = false;

        dropdown.forEach(item => {
            if (!item.hidden) {
                data.hasVisibleDropdownItems = true;
            }
        });

        data.noBreakWords = this.options.fontSizeFlexible;

        data.isXsSingleRow = this.options.isXsSingleRow;

        if ((data.items.buttons || []).length < 2) {
            data.isHeaderAdditionalSpace = true;
        }

        return data;
    }

    setup() {
        this.scope = this.options.scope;

        if (this.model) {
            this.listenTo(this.model, 'after:save', () => {
                if (this.isRendered()) {
                    this.reRender();
                }
            });
        }

        this.wasRendered = false;
    }


    afterRender() {
        if (this.options.fontSizeFlexible) {
            this.adjustFontSize();
        }

        if (this.wasRendered) {
            this.getParentMainView().trigger('header-rendered');
        }

        this.wasRendered = true;
    }

    adjustFontSize(step) {
        step = step || 0;

        if (!step) {
            this.fontSizePercentage = 100;
        }

        let $container = this.$el.find('.header-breadcrumbs');
        let containerWidth = $container.width();
        let childrenWidth = 0;

        $container.children().each((i, el) => {
            childrenWidth += $(el).outerWidth(true);
        });

        if (containerWidth < childrenWidth) {
            if (step > 7) {
                $container.addClass('overlapped');

                this.$el.find('.title').each((i, el) => {
                    let $el = $(el);
                    let text = $(el).text();

                    $el.attr('title', text);

                    let isInitialized = false;

                    $el.on('touchstart', () => {
                        if (!isInitialized) {
                            $el.attr('title', '');
                            isInitialized = true;

                            Espo.Ui.popover($el, {
                                content: text,
                                noToggleInit: true,
                            }, this);
                        }

                        $el.popover('toggle');
                    });
                });

                return;
            }

            this.fontSizePercentage -= 4;

            let $flexible = this.$el.find('.font-size-flexible');

            $flexible.css('font-size', this.fontSizePercentage + '%');
            $flexible.css('position', 'relative');

            if (step > 6) {
                $flexible.css('top', '-1px');
            } else if (step > 4) {
                $flexible.css('top', '-1px');
            }

            this.adjustFontSize(step + 1);
        }
    }

    getItems() {
        return this.getParentMainView().getMenu() || {};
    }

    /**
     * @return {module:views/main}
     */
    getParentMainView() {
        return /** @type module:views/main */this.getParentView();
    }
}

export default HeaderView;
PK]�$�00views/deleted-detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import DetailView from 'views/detail';

class DeletedDetailView extends DetailView {

    recordView = 'views/record/deleted-detail'

    menuDisabled = true

    setup() {
        super.setup();

        if (this.model.get('deleted')) {
            this.menuDisabled = true;
        }
    }

    getRecordViewName() {
        return this.recordView;
    }
}

// noinspection JSUnusedGlobalSymbols
export default DeletedDetailView;
PK]3����P�Pviews/import/step2.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';

class Step2ImportView extends View {

    template = 'import/step-2'

    allowedFieldList = ['createdAt', 'createdBy']

    events = {
        /** @this Step2ImportView */
        'click button[data-action="back"]': function () {
            this.back();
        },
        /** @this Step2ImportView */
        'click button[data-action="next"]': function () {
            this.next();
        },
        /** @this Step2ImportView */
        'click a[data-action="addField"]': function (e) {
            let field = $(e.currentTarget).data('name');

            this.addField(field);
        },
        /** @this Step2ImportView */
        'click a[data-action="removeField"]': function (e) {
            let field = $(e.currentTarget).data('name');

            this.$el.find('a[data-action="addField"]').parent().removeClass('hidden');

            let index = this.additionalFields.indexOf(field);

            if (~index) {
                this.additionalFields.splice(index, 1);
            }

            this.$el.find('.field[data-name="' + field + '"]').parent().remove();
        },
    }

    data() {
        return {
            scope: this.scope,
            fieldList: this.getFieldList(),
        };
    }

    setup() {
        this.formData = this.options.formData;
        this.scope = this.formData.entityType;

        let mapping = [];

        this.additionalFields = [];

        if (this.formData.previewArray) {
            let index = 0;

            if (this.formData.headerRow) {
                index = 1;
            }

            if (this.formData.previewArray.length > index) {
                this.formData.previewArray[index].forEach((value, i) => {
                    let d = {value: value};

                    if (this.formData.headerRow) {
                        d.name = this.formData.previewArray[0][i];
                    }

                    mapping.push(d);
                });
            }
        }

        this.wait(true);

        this.getModelFactory().create(this.scope, model => {
            this.model = model;

            if (this.formData.defaultValues) {
                this.model.set(this.formData.defaultValues);
            }

            this.wait(false);
        });

        this.mapping = mapping;
    }

    afterRender() {
        let $container = $('#mapping-container');

        let $table = $('<table>')
            .addClass('table')
            .addClass('table-bordered')
            .css('table-layout', 'fixed');

        let $tbody = $('<tbody>').appendTo($table);

        let $row = $('<tr>');

        if (this.formData.headerRow) {
            let $cell = $('<th>')
                .attr('width', '25%')
                .text(this.translate('Header Row Value', 'labels', 'Import'));

            $row.append($cell);
        }

        let $cell = $('<th>')
            .attr('width', '25%')
            .text(this.translate('Field', 'labels', 'Import'));

        $row.append($cell);

        $cell = $('<th>').text(this.translate('First Row Value', 'labels', 'Import'));

        $row.append($cell);

        if (~['update', 'createAndUpdate'].indexOf(this.formData.action)) {
            $cell = $('<th>').text(this.translate('Update by', 'labels', 'Import'));

            $row.append($cell);
        }

        $tbody.append($row);

        this.mapping.forEach((d, i) => {
            $row = $('<tr>');

            if (this.formData.headerRow) {
                $cell = $('<td>')
                    .text(d.name);

                $row.append($cell);
            }

            let selectedName = d.name;

            if (this.formData.attributeList) {
                if (this.formData.attributeList[i]) {
                    selectedName = this.formData.attributeList[i];
                } else {
                    selectedName = null;
                }
            }

            let $select = this.getFieldDropdown(i, selectedName);

            $cell = $('<td>').append($select);

            $row.append($cell);

            var value = d.value || '';

            if (value.length > 200) {
                value = value.substring(0, 200) + '...';
            }

            $cell = $('<td>')
                .css('overflow', 'hidden')
                .text(value);

            $row.append($cell);

            if (~['update', 'createAndUpdate'].indexOf(this.formData.action)) {
                let $checkbox = $('<input>')
                    .attr('type', 'checkbox')
                    .attr('id', 'update-by-' + i.toString());

                if (!this.formData.updateBy) {
                    if (d.name === 'id') {
                        $checkbox.attr('checked', true);
                    }
                }
                else if (~this.formData.updateBy.indexOf(i)) {
                    $checkbox.attr('checked', true);
                }

                $cell = $('<td>').append($checkbox);

                $row.append($cell);
            }

            $tbody.append($row);
        });

        $container.empty();
        $container.append($table);

        if (this.formData.defaultFieldList) {
            this.formData.defaultFieldList.forEach((name) => {
                this.addField(name);
            });
        }
    }

    getFieldList() {
        let defs = this.getMetadata().get('entityDefs.' + this.scope + '.fields');
        let forbiddenFieldList = this.getAcl().getScopeForbiddenFieldList(this.scope, 'edit');

        let fieldList = [];

        for (let field in defs) {
            if (~forbiddenFieldList.indexOf(field)) {
                continue;
            }

            let d = /** @type Object.<string, *> */defs[field];

            if (!~this.allowedFieldList.indexOf(field) && (d.disabled || d.importDisabled)) {
                continue;
            }

            fieldList.push(field);
        }

        fieldList = fieldList.sort((v1, v2) => {
            return this.translate(v1, 'fields', this.scope)
                .localeCompare(this.translate(v2, 'fields', this.scope));
        });

        return fieldList;
    }

    getAttributeList() {
        let fields = this.getMetadata().get(['entityDefs', this.scope, 'fields']) || {};
        let forbiddenFieldList = this.getAcl().getScopeForbiddenFieldList(this.scope, 'edit');

        let attributeList = [];

        attributeList.push('id');

        for (let field in fields) {
            if (~forbiddenFieldList.indexOf(field)) {
                continue;
            }

            let d = /** @type Object.<string, *> */fields[field];

            if (
                !~this.allowedFieldList.indexOf(field) &&
                (d.disabled && !d.importNotDisabled || d.importDisabled)
            ) {
                continue;
            }

            if (d.type === 'phone') {
                attributeList
                    .push(field);

                (this.getMetadata().get('entityDefs.' + this.scope + '.fields.' + field + '.typeList') || [])
                    .map((item) => {
                        return item.replace(/\s/g, '_');
                    })
                    .forEach((item) => {
                        attributeList.push(field + Espo.Utils.upperCaseFirst(item));
                    });

                continue;
            }

            if (d.type === 'email') {
                attributeList.push(field + '2');
                attributeList.push(field + '3');
                attributeList.push(field + '4');
            }

            if (d.type === 'link') {
                attributeList.push(field + 'Name');
                attributeList.push(field + 'Id');
            }

            if (~['foreign'].indexOf(d.type)) {
                continue;
            }

            if (d.type === 'personName') {
                attributeList.push(field);
            }

            var type = d.type;
            var actualAttributeList = this.getFieldManager().getActualAttributeList(type, field);

            if (!actualAttributeList.length) {
                actualAttributeList = [field];
            }

            actualAttributeList.forEach((f) => {
                if (attributeList.indexOf(f) === -1) {
                    attributeList.push(f);
                }
            });
        }

        attributeList = attributeList.sort((v1, v2) => {
            return this.translate(v1, 'fields', this.scope)
                .localeCompare(this.translate(v2, 'fields', this.scope));
        });

        return attributeList;
    }

    getFieldDropdown(num, name) {
        name = name || false;

        let fieldList = this.getAttributeList();

        let $select = $('<select>')
            .addClass('form-control')
            .attr('id', 'column-' + num.toString());

        let $option = $('<option>')
            .val('')
            .text('-' + this.translate('Skip', 'labels', 'Import') + '-');

        let scope = this.formData.entityType;

        $select.append($option);

        fieldList.forEach(field => {
            let label = '';

            if (
                this.getLanguage().has(field, 'fields', scope) ||
                this.getLanguage().has(field, 'fields', 'Global')
            ) {
                label = this.translate(field, 'fields', scope);
            }
            else {
                if (field.indexOf('Id') === field.length - 2) {
                    let baseField = field.substr(0, field.length - 2);

                    if (this.getMetadata().get(['entityDefs', scope, 'fields', baseField])) {
                        label = this.translate(baseField, 'fields', scope) +
                            ' (' + this.translate('id', 'fields') + ')';
                    }
                }
                else if (field.indexOf('Name') === field.length - 4) {
                    let baseField = field.substr(0, field.length - 4);

                    if (this.getMetadata().get(['entityDefs', scope, 'fields', baseField])) {
                        label = this.translate(baseField, 'fields', scope) +
                            ' (' + this.translate('name', 'fields') + ')';
                    }
                }
                else if (field.indexOf('Type') === field.length - 4) {
                    let baseField = field.substr(0, field.length - 4);

                    if (this.getMetadata().get(['entityDefs', scope, 'fields', baseField])) {
                        label = this.translate(baseField, 'fields', scope) +
                            ' (' + this.translate('type', 'fields') + ')';
                    }
                }
                else if (field.indexOf('phoneNumber') === 0) {
                    let phoneNumberType = field.substr(11);

                    let phoneNumberTypeLabel = this.getLanguage()
                        .translateOption(phoneNumberType, 'phoneNumber', scope);

                    label = this.translate('phoneNumber', 'fields', scope) +
                        ' (' + phoneNumberTypeLabel + ')';
                }
                else if (
                    field.indexOf('emailAddress') === 0 &&
                    parseInt(field.substr(12)).toString() === field.substr(12)
                ) {
                    let emailAddressNum = field.substr(12);

                    label = this.translate('emailAddress', 'fields', scope) + ' ' + emailAddressNum.toString();
                }
                else if (field.indexOf('Ids') === field.length - 3) {
                    let baseField = field.substr(0, field.length - 3);

                    if (this.getMetadata().get(['entityDefs', scope, 'fields', baseField])) {
                        label = this.translate(baseField, 'fields', scope) +
                            ' (' + this.translate('ids', 'fields') + ')';
                    }
                }
            }

            if (!label) {
                label = field;
            }

            $option = $('<option>')
                .val(field)
                .text(label);

            if (name) {
                if (field === name) {
                    $option.prop('selected', true);
                }
                else {
                    if (name.toLowerCase().replace('_', '') === field.toLowerCase()) {
                        $option.prop('selected', true);
                    }
                }
            }

            $select.append($option);
        });

        return $select;
    }

    addField(name) {
        this.$el.find('[data-action="addField"][data-name="' + name + '"]')
            .parent()
            .addClass('hidden');

        $(this.containerSelector + ' button[data-name="update"]').removeClass('disabled');

        Espo.Ui.notify(' ... ');

        let label = this.translate(name, 'fields', this.scope);
        label = this.getHelper().escapeString(label);

        let removeLink =
            '<a role="button" class="pull-right" data-action="removeField" data-name="' + name + '">' +
            '<span class="fas fa-times"></span></a>';

        let html =
            '<div class="cell form-group">' + removeLink + '<label class="control-label">' + label +
            '</label><div class="field" data-name="' + name + '"/></div>';

        $('#default-values-container').append(html);

        let type = Espo.Utils.upperCaseFirst(this.model.getFieldParam(name, 'type'));

        let viewName =
            this.getMetadata().get(['entityDefs', this.scope, 'fields', name, 'view']) ||
            this.getFieldManager().getViewName(type);

        this.createView(name, viewName, {
            model: this.model,
            fullSelector: this.getSelector() + ' .field[data-name="' + name + '"]',
            defs: {
                name: name,
            },
            mode: 'edit',
            readOnlyDisabled: true,
        }, view => {
            this.additionalFields.push(name);

            view.render();
            view.notify(false);
        });
    }

    disableButtons() {
        this.$el.find('button[data-action="next"]').addClass('disabled').attr('disabled', 'disabled');
        this.$el.find('button[data-action="back"]').addClass('disabled').attr('disabled', 'disabled');
    }

    enableButtons() {
        this.$el.find('button[data-action="next"]').removeClass('disabled').removeAttr('disabled');
        this.$el.find('button[data-action="back"]').removeClass('disabled').removeAttr('disabled');
    }

    /**
     * @param {string} field
     * @return {module:views/fields/base}
     */
    getFieldView(field) {
        return this.getView(field);
    }

    fetch(skipValidation) {
        let attributes = {};

        this.additionalFields.forEach(field => {
            const view = this.getFieldView(field);

            _.extend(attributes, view.fetch());
        });

        this.model.set(attributes);

        let notValid = false;

        this.additionalFields.forEach(field => {
            let view = this.getFieldView(field);

            notValid = view.validate() || notValid;
        });

        if (!notValid) {
            this.formData.defaultValues = attributes;
        }

        if (notValid && !skipValidation) {
            return false;
        }

        this.formData.defaultFieldList = Espo.Utils.clone(this.additionalFields);

        var attributeList = [];

        this.mapping.forEach((d, i) => {
            attributeList.push($('#column-' + i).val());
        });

        this.formData.attributeList = attributeList;

        if (~['update', 'createAndUpdate'].indexOf(this.formData.action)) {
            let updateBy = [];

            this.mapping.forEach((d, i) => {
                if ($('#update-by-' + i).get(0).checked) {
                    updateBy.push(i);
                }
            });

            this.formData.updateBy = updateBy;
        }

        this.getParentIndexView().formData = this.formData;
        this.getParentIndexView().trigger('change');

        return true;
    }

    /**
     * @return {import('./index').default}
     */
    getParentIndexView() {
        // noinspection JSValidateTypes
        return this.getParentView();
    }

    back() {
        this.fetch(true);

        this.getParentIndexView().changeStep(1);
    }

    next() {
        if (!this.fetch()) {
            return;
        }

        this.disableButtons();

        Espo.Ui.notify(' ... ');

        Espo.Ajax.postRequest('Import/file', null, {
            timeout: 0,
            contentType: 'text/csv',
            data: this.getParentIndexView().fileContents,
        }).then(result => {
            if (!result.attachmentId) {
                Espo.Ui.error(this.translate('Bad response'));

                return;
            }

            this.runImport(result.attachmentId);
        });
    }

    runImport(attachmentId) {
        this.formData.attachmentId = attachmentId;

        this.getRouter().confirmLeaveOut = false;

        Espo.Ui.notify(this.translate('importRunning', 'messages', 'Import'));

        Espo.Ajax.postRequest('Import', this.formData, {timeout: 0})
            .then(result => {
                const id = result.id;

                this.getParentIndexView().trigger('done');

                if (!id) {
                    Espo.Ui.error(this.translate('Error'), true);

                    this.enableButtons();

                    return;
                }

                if (!this.formData.manualMode) {
                    this.getRouter().navigate('#Import/view/' + id, {trigger: true});

                    Espo.Ui.notify(false);

                    return;
                }

                this.createView('dialog', 'views/modal', {
                    templateContent: "{{complexText viewObject.options.msg}}",
                    headerText: ' ',
                    backdrop: 'static',
                    msg:
                        this.translate('commandToRun', 'strings', 'Import') + ':\n\n' +
                        '```php command.php import --id=' + id + '```',
                    buttonList: [
                        {
                            name: 'close',
                            label: this.translate('Close'),
                        }
                    ],
                }, view => {
                    view.render();

                    this.listenToOnce(view, 'close', () => {
                        this.getRouter().navigate('#Import/view/' + id, {trigger: true});
                    });
                });

                Espo.Ui.notify(false);
            })
            .catch(() => this.enableButtons());
    }
}

export default Step2ImportView;
PK]��c}�O�Oviews/import/step1.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';
import Model from 'model';

class Step1ImportView extends View {

    template = 'import/step-1'

    events = {
        /** @this Step1ImportView */
        'change #import-file': function (e) {
            let files = e.currentTarget.files;

            if (files.length) {
                this.loadFile(files[0]);
            }
        },
        /** @this Step1ImportView */
        'click button[data-action="next"]': function () {
            this.next();
        },
        /** @this Step1ImportView */
        'click button[data-action="saveAsDefault"]': function () {
            this.saveAsDefault();
        },
    }

    getEntityList() {
        let list = [];
        let scopes = this.getMetadata().get('scopes');

        for (let scopeName in scopes) {
            if (scopes[scopeName].importable) {
                if (!this.getAcl().checkScope(scopeName, 'create')) {
                    continue;
                }

                list.push(scopeName);
            }
        }

        list.sort((v1, v2) => {
             return this.translate(v1, 'scopeNamesPlural')
                 .localeCompare(this.translate(v2, 'scopeNamesPlural'));
        });

        return list;
    }

    data() {
        return {
            entityList: this.getEntityList(),
        };
    }

    setup() {
        this.attributeList = [
            'entityType',
            'action',
        ];

        this.paramList = [
            'headerRow',
            'decimalMark',
            'personNameFormat',
            'delimiter',
            'dateFormat',
            'timeFormat',
            'currency',
            'timezone',
            'textQualifier',
            'silentMode',
            'idleMode',
            'skipDuplicateChecking',
            'manualMode',
        ];

        this.paramList.forEach(item => {
            this.attributeList.push(item);
        });

        this.formData = this.options.formData || {
            entityType: this.options.entityType || null,
            create: 'create',
            headerRow: true,
            delimiter: ',',
            textQualifier: '"',
            dateFormat: 'YYYY-MM-DD',
            timeFormat: 'HH:mm:ss',
            currency: this.getConfig().get('defaultCurrency'),
            timezone: 'UTC',
            decimalMark: '.',
            personNameFormat: 'f l',
            idleMode: false,
            skipDuplicateChecking: false,
            silentMode: true,
            manualMode: false,
        };

        let defaults = Espo.Utils.cloneDeep(
            (this.getPreferences().get('importParams') || {}).default || {}
        );

        if (!this.options.formData) {
            for (let p in defaults) {
                this.formData[p] = defaults[p];
            }
        }

        let model = this.model = new Model;

        this.attributeList.forEach(a => {
            model.set(a, this.formData[a]);
        });

        this.attributeList.forEach(a => {
            this.listenTo(model, 'change:' + a, (m, v, o) => {
                if (!o.ui) {
                    return;
                }

                this.formData[a] = this.model.get(a);

                this.preview();
            });
        });

        let personNameFormatList = [
            'f l',
            'l f',
            'l, f',
        ];

        let personNameFormat = this.getConfig().get('personNameFormat') || 'firstLast';

        if (~personNameFormat.toString().toLowerCase().indexOf('middle')) {
            personNameFormatList.push('f m l');
            personNameFormatList.push('l f m');
        }

        let dateFormatDataList = this.getDateFormatDataList();
        let timeFormatDataList = this.getTimeFormatDataList();

        let dateFormatList = [];
        let dateFormatOptions = {};

        dateFormatDataList.forEach(item => {
            dateFormatList.push(item.key);

            dateFormatOptions[item.key] = item.label;
        });

        let timeFormatList = [];
        let timeFormatOptions = {};

        timeFormatDataList.forEach(item => {
            timeFormatList.push(item.key);

            timeFormatOptions[item.key] = item.label;
        });

        this.createView('actionField', 'views/fields/enum', {
            selector: '.field[data-name="action"]',
            model: this.model,
            name: 'action',
            mode: 'edit',
            params: {
                options: [
                    'create',
                    'createAndUpdate',
                    'update',
                ],
                translatedOptions: {
                    create: this.translate('Create Only', 'labels', 'Admin'),
                    createAndUpdate: this.translate('Create and Update', 'labels', 'Admin'),
                    update: this.translate('Update Only', 'labels', 'Admin'),
                },
            },
        });

        this.createView('entityTypeField', 'views/fields/enum', {
            selector: '.field[data-name="entityType"]',
            model: this.model,
            name: 'entityType',
            mode: 'edit',
            params: {
                options: [''].concat(this.getEntityList()),
                translation: 'Global.scopeNamesPlural',
                required: true,
            },
            labelText: this.translate('Entity Type', 'labels', 'Import'),
        });

        this.createView('decimalMarkField', 'views/fields/varchar', {
            selector: '.field[data-name="decimalMark"]',
            model: this.model,
            name: 'decimalMark',
            mode: 'edit',
            params: {
                options: [
                    '.',
                    ',',
                ],
                maxLength: 1,
                required: true,
            },
            labelText: this.translate('Decimal Mark', 'labels', 'Import'),
        });

        this.createView('personNameFormatField', 'views/fields/enum', {
            selector: '.field[data-name="personNameFormat"]',
            model: this.model,
            name: 'personNameFormat',
            mode: 'edit',
            params: {
                options: personNameFormatList,
                translation: 'Import.options.personNameFormat',
            },
        });

        this.createView('delimiterField', 'views/fields/enum', {
            selector: '.field[data-name="delimiter"]',
            model: this.model,
            name: 'delimiter',
            mode: 'edit',
            params: {
                options: [
                    ',',
                    ';',
                    '\\t',
                    '|',
                ],
            },
        });

        this.createView('textQualifierField', 'views/fields/enum', {
            selector: '.field[data-name="textQualifier"]',
            model: this.model,
            name: 'textQualifier',
            mode: 'edit',
            params: {
                options: ['"', '\''],
                translatedOptions: {
                    '"': this.translate('Double Quote', 'labels', 'Import'),
                    '\'': this.translate('Single Quote', 'labels', 'Import'),
                },
            },
        });

        this.createView('dateFormatField', 'views/fields/enum', {
            selector: '.field[data-name="dateFormat"]',
            model: this.model,
            name: 'dateFormat',
            mode: 'edit',
            params: {
                options: dateFormatList,
                translatedOptions: dateFormatOptions,
            },
        });

        this.createView('timeFormatField', 'views/fields/enum', {
            selector: '.field[data-name="timeFormat"]',
            model: this.model,
            name: 'timeFormat',
            mode: 'edit',
            params: {
                options: timeFormatList,
                translatedOptions: timeFormatOptions,
            },
        });

        this.createView('currencyField', 'views/fields/enum', {
            selector: '.field[data-name="currency"]',
            model: this.model,
            name: 'currency',
            mode: 'edit',
            params: {
                options: this.getConfig().get('currencyList'),
            },
        });

        this.createView('timezoneField', 'views/fields/enum', {
            selector: '.field[data-name="timezone"]',
            model: this.model,
            name: 'timezone',
            mode: 'edit',
            params: {
                options: this.getMetadata().get(['entityDefs', 'Settings', 'fields', 'timeZone', 'options']),
            },
        });

        this.createView('headerRowField', 'views/fields/bool', {
            selector: '.field[data-name="headerRow"]',
            model: this.model,
            name: 'headerRow',
            mode: 'edit',
        });

        this.createView('silentModeField', 'views/fields/bool', {
            selector: '.field[data-name="silentMode"]',
            model: this.model,
            name: 'silentMode',
            mode: 'edit',
            tooltip: true,
            tooltipText: this.translate('silentMode', 'tooltips', 'Import'),
        });

        this.createView('idleModeField', 'views/fields/bool', {
            selector: '.field[data-name="idleMode"]',
            model: this.model,
            name: 'idleMode',
            mode: 'edit',
        });

        this.createView('skipDuplicateCheckingField', 'views/fields/bool', {
            selector: '.field[data-name="skipDuplicateChecking"]',
            model: this.model,
            name: 'skipDuplicateChecking',
            mode: 'edit',
        });

        this.createView('manualModeField', 'views/fields/bool', {
            selector: '.field[data-name="manualMode"]',
            model: this.model,
            name: 'manualMode',
            mode: 'edit',
            tooltip: true,
            tooltipText: this.translate('manualMode', 'tooltips', 'Import'),
        });

        this.listenTo(this.model, 'change', (m, o) => {
            if (!o.ui) {
                return;
            }

            let isParamChanged = false;

            this.paramList.forEach(a => {
                if (m.hasChanged(a)) {
                    isParamChanged = true;
                }
            });

            if (isParamChanged) {
                this.showSaveAsDefaultButton();
            }
        });

        this.listenTo(this.model, 'change', () => {
            if (this.isRendered()) {
                this.controlFieldVisibility();
            }
        });

        this.listenTo(this.model, 'change:entityType', () => {
            delete this.formData.defaultFieldList;
            delete this.formData.defaultValues;
            delete this.formData.attributeList;
            delete this.formData.updateBy;
        });

        this.listenTo(this.model, 'change:action', () => {
            delete this.formData.updateBy;
        });

        this.listenTo(this.model, 'change', (m, o) => {
            if (!o.ui) {
                return;
            }

            this.getRouter().confirmLeaveOut = true;
        });
    }

    afterRender() {
        this.setupFormData();

        if (this.getParentIndexView() && this.getParentIndexView().fileContents) {
            this.setFileIsLoaded();
            this.preview();
        }

        this.controlFieldVisibility();
    }

    /**
     * @return {import('./index').default}
     */
    getParentIndexView() {
        // noinspection JSValidateTypes
        return this.getParentView();
    }

    showSaveAsDefaultButton() {
        this.$el.find('[data-action="saveAsDefault"]').removeClass('hidden');
    }

    hideSaveAsDefaultButton() {
        this.$el.find('[data-action="saveAsDefault"]').addClass('hidden');
    }

    /**
     * @return {module:views/fields/base}
     */
    getFieldView(field) {
        return this.getView(field + 'Field');
    }

    next() {
        this.attributeList.forEach(field => {
            this.getFieldView(field).fetchToModel();

            this.formData[field] = this.model.get(field);
        });

        let isInvalid = false;

        this.attributeList.forEach(field => {
            isInvalid |= this.getFieldView(field).validate();
        });

        if (isInvalid) {
            Espo.Ui.error(this.translate('Not valid'));

            return;
        }

        this.getParentIndexView().formData = this.formData;
        this.getParentIndexView().trigger('change');
        this.getParentIndexView().changeStep(2);
    }

    setupFormData() {
        this.attributeList.forEach(field => {
            this.model.set(field, this.formData[field]);
        });
    }

    /**
     * @param {File} file
     */
    loadFile(file) {
        let blob = file.slice(0, 1024 * 16);

        let readerPreview = new FileReader();

        readerPreview.onloadend = e => {
            if (e.target.readyState === FileReader.DONE) {
                this.formData.previewString = e.target.result;

                this.preview();
            }
        };

        readerPreview.readAsText(blob);

        let reader = new FileReader();

        reader.onloadend = e => {
            if (e.target.readyState === FileReader.DONE) {
                this.getParentIndexView().fileContents = e.target.result;

                this.setFileIsLoaded();

                this.getRouter().confirmLeaveOut = true;

                this.setFileName(file.name);
            }
        };

        reader.readAsText(file);
    }

    /**
     * @param {string} name
     */
    setFileName(name) {
        this.$el.find('.import-file-name').text(name);
        this.$el.find('.import-file-info').text('');
    }

    setFileIsLoaded() {
        this.$el.find('button[data-action="next"]').removeClass('hidden');
    }

    preview() {
        if (!this.formData.previewString) {
            return;
        }

        let arr = this.csvToArray(
            this.formData.previewString,
            this.formData.delimiter,
            this.formData.textQualifier
        );

        this.formData.previewArray = arr;

        let $table = $('<table>').addClass('table').addClass('table-bordered');
        let $tbody = $('<tbody>').appendTo($table);

        arr.forEach((row, i) => {
            if (i >= 3) {
                return;
            }

            let $row = $('<tr>');

            row.forEach((value) => {
                let $cell = $('<td>').html(this.getHelper().sanitizeHtml(value));

                $row.append($cell);
            });

            $tbody.append($row);
        });

        let $container = $('#import-preview');

        $container.empty().append($table);
    }

    csvToArray(strData, strDelimiter, strQualifier) {
        strDelimiter = (strDelimiter || ',');
        strQualifier = (strQualifier || '\"');

        strDelimiter = strDelimiter.replace(/\\t/, '\t');

        let objPattern = new RegExp(
            (
                // Delimiters.
                "(\\" + strDelimiter + "|\\r?\\n|\\r|^)" +

                // Quoted fields.
                "(?:"+strQualifier+"([^"+strQualifier+"]*(?:"+strQualifier+""+strQualifier+
                    "[^"+strQualifier+"]*)*)"+strQualifier+"|" +

                // Standard fields.
                "([^"+strQualifier+"\\" + strDelimiter + "\\r\\n]*))"
            ),
            "gi"
        );

        let arrData = [[]];
        let arrMatches = null;

        while (arrMatches = objPattern.exec(strData)) {
            let strMatchedDelimiter = arrMatches[1];
            let strMatchedValue;

            if (
                strMatchedDelimiter.length &&
                (strMatchedDelimiter !== strDelimiter)
            ) {
                arrData.push([]);
            }

            if (arrMatches[2]) {
                strMatchedValue = arrMatches[2].replace(new RegExp( "\"\"", "g" ),  "\"");
            } else {
                strMatchedValue = arrMatches[3];
            }

            arrData[arrData.length - 1].push(strMatchedValue);
        }

        return arrData;
    }

    saveAsDefault() {
        let preferences = this.getPreferences();

        let importParams = Espo.Utils.cloneDeep(preferences.get('importParams') || {});

        let data = {};

        this.paramList.forEach(attribute => {
            data[attribute] = this.model.get(attribute);
        });

        importParams.default = data;

        preferences.save({importParams: importParams})
            .then(() => {
                Espo.Ui.success(this.translate('Saved'))
            });

        this.hideSaveAsDefaultButton();
    }

    controlFieldVisibility() {
        if (this.model.get('idleMode')) {
            this.hideField('manualMode');
        } else {
            this.showField('manualMode');
        }

        if (this.model.get('manualMode')) {
            this.hideField('idleMode');
        } else {
            this.showField('idleMode');
        }
    }

    hideField(name) {
        this.$el.find('.field[data-name="'+name+'"]').parent().addClass('hidden-cell');
    }

    showField(name) {
        this.$el.find('.field[data-name="'+name+'"]').parent().removeClass('hidden-cell');
    }

    convertFormatToLabel(format) {
        let formatItemLabelMap = {
            'YYYY': '2021',
            'DD': '27',
            'MM': '12',
            'HH': '23',
            'mm': '00',
            'hh': '11',
            'ss': '00',
            'a': 'pm',
            'A': 'PM',
        };

        let label = format;

        for (let item in formatItemLabelMap) {
            let value = formatItemLabelMap[item];

            label = label.replace(new RegExp(item, 'g'), value);
        }

        return format + ' - ' + label;
    }

    getDateFormatDataList() {
        let dateFormatList = this.getMetadata().get(['clientDefs', 'Import', 'dateFormatList']) || [];

        return dateFormatList.map(item => {
            return {
                key: item,
                label: this.convertFormatToLabel(item),
            };
        });
    }

    getTimeFormatDataList() {
        let timeFormatList = this.getMetadata().get(['clientDefs', 'Import', 'timeFormatList']) || [];

        return timeFormatList.map(item => {
            return {
                key: item,
                label: this.convertFormatToLabel(item),
            };
        });
    }
}

export default Step1ImportView;
PK]io�LXXviews/import/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import DetailView from 'views/detail';

class ImportDetailView extends DetailView {

    getHeader() {
        let name = this.getDateTime().toDisplay(this.model.get('createdAt'));

        return this.buildHeaderHtml([
            $('<a>')
                .attr('href', '#' + this.model.entityType + '/list')
                .text(this.getLanguage().translate(this.model.entityType, 'scopeNamesPlural')),
            $('<span>')
                .text(name)
        ]);
    }

    setup() {
        super.setup();

        this.setupMenu();

        this.listenTo(this.model, 'change', () => {
            this.setupMenu();

            if (this.isRendered()) {
                this.getView('header').reRender();
            }
        });

        this.listenTo(this.model, 'sync', (m) => {
            this.controlButtons(m);
        });
    }

    setupMenu() {
        this.addMenuItem('buttons', {
            label: "Remove Import Log",
            action: "removeImportLog",
            name: 'removeImportLog',
            style: "default",
            acl: "delete",
            title: this.translate('removeImportLog', 'messages', 'Import'),
        }, true);

        this.addMenuItem('buttons', {
            label: "Revert Import",
            name: 'revert',
            action: "revert",
            style: "danger",
            acl: "edit",
            title: this.translate('revert', 'messages', 'Import'),
            hidden: !this.model.get('importedCount'),
        }, true);

        this.addMenuItem('buttons', {
            label: "Remove Duplicates",
            name: 'removeDuplicates',
            action: "removeDuplicates",
            style: "default",
            acl: "edit",
            title: this.translate('removeDuplicates', 'messages', 'Import'),
            hidden: !this.model.get('duplicateCount'),
        }, true);

        this.addMenuItem('dropdown', {
            label: 'New import with same params',
            name: 'createWithSameParams',
            action: 'createWithSameParams',
        });
    }

    controlButtons(model) {
        if (!model || model.hasChanged('importedCount')) {
            if (this.model.get('importedCount')) {
                this.showHeaderActionItem('revert');
            } else {
                this.hideHeaderActionItem('revert');
            }
        }

        if (!model || model.hasChanged('duplicateCount')) {
            if (this.model.get('duplicateCount')) {
                this.showHeaderActionItem('removeDuplicates');
            } else {
                this.hideHeaderActionItem('removeDuplicates');
            }
        }
    }

    // noinspection JSUnusedGlobalSymbols
    actionRemoveImportLog() {
        this.confirm(this.translate('confirmRemoveImportLog', 'messages', 'Import'), () => {
            this.disableMenuItem('removeImportLog');

            Espo.Ui.notify(this.translate('pleaseWait', 'messages'));

            this.model.destroy({
                wait: true,
            }).then(() => {
                Espo.Ui.notify(false);

                var collection = this.model.collection;

                if (collection) {
                    if (collection.total > 0) {
                        collection.total--;
                    }
                }

                this.getRouter().navigate('#Import/list', {trigger: true});

                this.removeMenuItem('removeImportLog', true);
            });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionRevert() {
        this.confirm(this.translate('confirmRevert', 'messages', 'Import'), () => {
            this.disableMenuItem('revert');

            Espo.Ui.notify(this.translate('pleaseWait', 'messages'));

            Espo.Ajax
                .postRequest(`Import/${this.model.id}/revert`)
                .then(() => {
                    this.getRouter().navigate('#Import/list', {trigger: true});
                });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionRemoveDuplicates() {
        this.confirm(this.translate('confirmRemoveDuplicates', 'messages', 'Import'), () => {
            this.disableMenuItem('removeDuplicates');

            Espo.Ui.notify(this.translate('pleaseWait', 'messages'));

            Espo.Ajax
                .postRequest(`Import/${this.model.id}/removeDuplicates`)
                .then(() => {
                    this.removeMenuItem('removeDuplicates', true);

                    this.model.fetch();
                    this.model.trigger('update-all');

                    Espo.Ui.success(this.translate('duplicatesRemoved', 'messages', 'Import'));
                });
            });
    }

    // noinspection JSUnusedGlobalSymbols
    actionCreateWithSameParams() {
        let formData = this.model.get('params') || {};

        formData.entityType = this.model.get('entityType');
        formData.attributeList = this.model.get('attributeList') || [];

        formData = Espo.Utils.cloneDeep(formData);

        this.getRouter().navigate('#Import', {trigger: false});

        this.getRouter().dispatch('Import', 'index', {
            formData: formData,
        });
    }
}

export default ImportDetailView;
PK]���QQ&views/import/record/panels/imported.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import RelationshipPanelView from 'views/record/panels/relationship';

class ImportImportedPanelView extends RelationshipPanelView {

    link = 'imported'
    readOnly = true
    rowActionsView = 'views/record/row-actions/relationship-no-unlink'

    setup() {
        this.scope = this.model.get('entityType');
        this.title = this.title || this.translate('Imported', 'labels', 'Import');

        super.setup();
    }
}

export default ImportImportedPanelView;

PK]�U���(views/import/record/panels/duplicates.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ImportImportedPanelView from 'views/import/record/panels/imported';

class ImportDuplicatesPanelView extends ImportImportedPanelView {

    link = 'duplicates'

    setup() {
        this.title = this.title || this.translate('Duplicates', 'labels', 'Import');

        super.setup();
    }

    // noinspection JSUnusedGlobalSymbols
    actionUnmarkAsDuplicate(data) {
        const id = data.id;
        const type = data.type;

        this.confirm(this.translate('confirmation', 'messages'), () => {
            Espo.Ajax.postRequest(`Import/${this.model.id}/unmarkDuplicates`, {
                entityId: id,
                entityType: type,
            }).then(() => {
                this.collection.fetch();
            });
        });
    }
}

export default ImportDuplicatesPanelView;
PK]R��

%views/import/record/panels/updated.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ImportImportedPanelView from 'views/import/record/panels/imported';

class ImportUpdatedPanelView extends ImportImportedPanelView {

    link = 'updated'
    rowActionsView = 'views/record/row-actions/relationship-view-and-edit'

    setup() {
        this.title = this.title || this.translate('Updated', 'labels', 'Import');

        super.setup();
    }
}

export default ImportUpdatedPanelView;
PK]��e��-views/import/record/row-actions/duplicates.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import DefaultRowActionsView from 'views/record/row-actions/default';

class ImportDuplicatesRowActionsView extends DefaultRowActionsView {

    getActionList() {
        const list = super.getActionList();

        list.push({
            action: 'unmarkAsDuplicate',
            label: 'Set as Not Duplicate',
            data: {
                id: this.model.id,
                type: this.model.entityType,
            },
        });

        return list;
    }
}

export default ImportDuplicatesRowActionsView;
PK];�R�;;views/import/record/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import DetailRecordView from 'views/record/detail';

class ImportDetailRecordView extends DetailRecordView {

    readOnly = true
    returnUrl = '#Import/list'
    checkInterval = 5
    resultPanelFetchLimit = 10
    duplicateAction = false

    setup() {
        super.setup();

        this.fetchCounter = 0;
        this.setupChecking();

        this.hideActionItem('delete');
    }

    setupChecking() {
        if (!this.model.has('status')) {
            this.listenToOnce(this.model, 'sync', this.setupChecking.bind(this));

            return;
        }

        if (!~['In Process', 'Pending', 'Standby'].indexOf(this.model.get('status'))) {
            return;
        }

        setTimeout(this.runChecking.bind(this), this.checkInterval * 1000);

        this.on('remove', () => {
            this.stopChecking = true;
        });
    }

    runChecking() {
        if (this.stopChecking) {
            return;
        }

        this.model.fetch().then(() => {
            const isFinished = !~['In Process', 'Pending', 'Standby'].indexOf(this.model.get('status'));

            if (this.fetchCounter < this.resultPanelFetchLimit && !isFinished) {
                this.fetchResultPanels();
            }

            if (isFinished) {
                this.fetchResultPanels();

                return;
            }

            setTimeout(this.runChecking.bind(this), this.checkInterval * 1000);
        });

        this.fetchCounter++;
    }

    fetchResultPanels() {
        const bottomView = this.getView('bottom');

        if (!bottomView) {
            return;
        }

        const importedView = bottomView.getView('imported');

        if (importedView && importedView.collection) {
            importedView.collection.fetch();
        }

        const duplicatesView = bottomView.getView('duplicates');

        if (duplicatesView && duplicatesView.collection) {
            duplicatesView.collection.fetch();
        }

        const updatedView = bottomView.getView('updated');

        if (updatedView && updatedView.collection) {
            updatedView.collection.fetch();
        }
    }
}

export default ImportDetailRecordView;
PK]�6���views/import/record/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ListRecordView from 'views/record/list';

class ImportListRecordView extends ListRecordView {

    quickDetailDisabled = true
    quickEditDisabled = true
    checkAllResultDisabled = true
    massActionList = ['remove']
    rowActionsView = 'views/record/row-actions/remove-only'
}

export default ImportListRecordView;
PK]���views/import/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ListView from 'views/list';

class ImportListView extends ListView {

    createButton = false

    setup() {
        super.setup();

        this.menu.buttons.unshift({
            iconHtml: '<span class="fas fa-plus fa-sm"></span>',
            text: this.translate('New Import', 'labels', 'Import'),
            link: '#Import',
            acl: 'edit',
        });
    }
}

export default ImportListView;
PK]�rK��views/import/index.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/import/index */

import View from 'view';

class IndexImportView extends View {

    template = 'import/index'

    formData = null
    fileContents = null

    data() {
        return {
            fromAdmin: this.options.fromAdmin,
        };
    }

    setup() {
        this.entityType = this.options.entityType || null;

        this.startFromStep = 1;

        if (this.options.formData || this.options.fileContents) {
            this.formData = this.options.formData || {};
            this.fileContents = this.options.fileContents || null;

            this.entityType = this.formData.entityType || null;

            if (this.options.step) {
                this.startFromStep = this.options.step;
            }
        }
    }

    changeStep(num, result) {
        this.step = num;

        if (num > 1) {
            this.setConfirmLeaveOut(true);
        }

        this.createView('step', 'views/import/step' + num.toString(), {
            selector: '> .import-container',
            entityType: this.entityType,
            formData: this.formData,
            result: result,
        }, view => {
            view.render();
        });

        var url = '#Import';

        if (this.options.fromAdmin) {
            url = '#Admin/import';
        }

        if (this.step > 1) {
            url += '/index/step=' + this.step;
        }

        this.getRouter().navigate(url, {trigger: false});
    }

    afterRender() {
        this.changeStep(this.startFromStep);
    }

    updatePageTitle() {
        this.setPageTitle(this.getLanguage().translate('Import', 'labels', 'Admin'));
    }

    setConfirmLeaveOut(value) {
        this.getRouter().confirmLeaveOut = value;
    }
}

export default IndexImportView;
PK]lC��
�
$views/wysiwyg/modals/insert-image.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/wysiwyg/modals/insert-image', ['views/modal'], function (Dep) {

    return Dep.extend({

        className: 'dialog dialog-record',

        template: 'wysiwyg/modals/insert-image',

        events: {
            'click [data-action="insert"]': function () {
                this.actionInsert();
            },
            'input [data-name="url"]': function () {
                this.controlInsertButton();
            },
            'paste [data-name="url"]': function () {
                this.controlInsertButton();
            },
        },

        shortcutKeys: {
            'Control+Enter': function () {
                if (!this.$el.find('[data-name="insert"]').hasClass('disabled')) {
                    this.actionInsert();
                }
            },
        },

        data: function () {
            return {
                labels: this.options.labels || {},
            };
        },

        setup: function () {
            let labels = this.options.labels || {};

            this.headerText = labels.insert;

            this.buttonList = [];
        },

        afterRender: function () {
            let $files = this.$el.find('[data-name="files"]');

            $files.replaceWith(
                $files.clone()
                    .on('change', (e) => {
                      this.trigger('upload', e.target.files || e.target.value);
                      this.close();
                    })
                    .val('')
            );
        },

        controlInsertButton: function () {
            let value = this.$el.find('[data-name="url"]').val().trim();

            let $button = this.$el.find('[data-name="insert"]');

            if (value) {
                $button.removeClass('disabled').removeAttr('disabled');
            } else {
                $button.addClass('disabled').attr('disabled', 'disabled');
            }
        },

        actionInsert: function () {
            let url = this.$el.find('[data-name="url"]').val().trim();

            this.trigger('insert', url);
            this.close();
        },
    });
});
PK]!Ct���#views/wysiwyg/modals/insert-link.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/wysiwyg/modals/insert-link', ['views/modal'], function (Dep) {

    return Dep.extend({

        className: 'dialog dialog-record',

        template: 'wysiwyg/modals/insert-link',

        events: {
            'input [data-name="url"]': function () {
                this.controlInputs();
            },
            'paste [data-name="url"]': function () {
                this.controlInputs();
            },
        },

        shortcutKeys: {
            'Control+Enter': function () {
                if (this.hasAvailableActionItem('insert')) {
                    this.actionInsert();
                }
            },
        },

        data: function () {
            return {
                labels: this.options.labels || {},
            };
        },

        setup: function () {
            let labels = this.options.labels || {};

            this.headerText = labels.insert;

            this.buttonList = [
                {
                    name: 'insert',
                    text: this.translate('Insert'),
                    style: 'primary',
                    disabled: true,
                }
            ];

            this.linkInfo = this.options.linkInfo || {};

            if (this.linkInfo.url) {
                this.enableButton('insert');
            }
        },

        afterRender: function () {
            this.$url = this.$el.find('[data-name="url"]');
            this.$text = this.$el.find('[data-name="text"]');
            this.$openInNewWindow = this.$el.find('[data-name="openInNewWindow"]');

            let linkInfo = this.linkInfo;

            this.$url.val(linkInfo.url || '');
            this.$text.val(linkInfo.text || '');

            if ('isNewWindow' in linkInfo) {
                this.$openInNewWindow.get(0).checked = !!linkInfo.isNewWindow;
            }
        },

        controlInputs: function () {
            let url = this.$url.val().trim();

            if (url) {
                this.enableButton('insert');
            } else {
                this.disableButton('insert');
            }
        },

        actionInsert: function () {
            let url = this.$url.val().trim();
            let text = this.$text.val().trim();
            let openInNewWindow = this.$openInNewWindow.get(0).checked;

            let data = {
                url: url,
                text: text || url,
                isNewWindow: openInNewWindow,
                range: this.linkInfo.range,
            };

            this.trigger('insert', data);
            this.close();
        },
    });
});
PK]�1/��(views/import-error/fields/line-number.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/import-error/fields/line-number', ['views/fields/int'], (Dep) => {

    return Dep.extend({

        disableFormatting: true,

        data: function () {
            let data = Dep.prototype.data.call(this);

            data.valueIsSet = this.model.has(this.sourceName);
            data.isNotEmpty = this.model.has(this.sourceName);

            return data;
        },

        setup: function () {
            Dep.prototype.setup.call(this);

            this.sourceName = this.name === 'exportLineNumber' ?
                'exportRowIndex' :
                'rowIndex';
        },

        getAttributeList: function () {
            return [this.sourceName];
        },

        getValueForDisplay: function () {
            let value = this.model.get(this.sourceName);

            value++;

            return this.formatNumber(value);
        },
    });
});
PK]����xx0views/import-error/fields/validation-failures.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/import-error/fields/validation-failures', ['views/fields/base'], (Dep) => {

    /**
     * @class
     * @name Class
     * @extends module:views/fields/base
     * @memberOf module:views/import-error/fields/validation-failures
     */
    return Dep.extend(/** @lends module:views/import-error/fields/validation-failures.Class# */{

        detailTemplateContent: `
            {{#if itemList.length}}
            <table class="table">
                <thead>
                    <tr>
                        <th style="width: 50%;">{{translate 'Field'}}</th>
                        <th>{{translateOption 'Validation' scope='ImportError' field='type'}}</th>
                    </tr>
                </thead>
                <tbody>
                    {{#each itemList}}
                    <tr>
                        <td>{{translate field category='fields' scope=entityType}}</td>
                        <td>
                            {{translate type category='fieldValidations'}}
                            {{#if popoverText}}
                            <a
                                role="button"
                                tabindex="-1"
                                class="text-muted popover-anchor"
                                data-text="{{popoverText}}"
                            ><span class="fas fa-info-circle"></span></a>
                            {{/if}}
                        </td>
                    </tr>
                    {{/each}}
                </tbody>
            </table>
            {{else}}
            <span class="none-value">{{translate 'None'}}</span>
            {{/if}}
        `,

        data: function () {
            let data = Dep.prototype.data.call(this);

            data.itemList = this.getDataList();

            return data;
        },

        afterRenderDetail: function () {
            this.$el.find('.popover-anchor').each((i, el) => {
                let text = this.getHelper().transformMarkdownText(el.dataset.text).toString();

                Espo.Ui.popover($(el), {content: text}, this);
            });
        },

        /**
         * @return {Object[]}
         */
        getDataList: function () {
            let itemList = Espo.Utils.cloneDeep(this.model.get(this.name)) || [];

            let entityType = this.model.get('entityType');

            if (Array.isArray(itemList)) {
                itemList.forEach(item => {
                    /** @var {module:field-manager} */
                    let fieldManager = this.getFieldManager();
                    /** @var {module:language} */
                    let language = this.getLanguage();

                    let fieldType = fieldManager.getEntityTypeFieldParam(entityType, item.field, 'type');

                    if (!fieldType) {
                        return;
                    }

                    let key = fieldType + '_' + item.type;

                    if (!language.has(key, 'fieldValidationExplanations', 'Global')) {
                        return;
                    }

                    item.popoverText = language.translate(key, 'fieldValidationExplanations');
                });
            }

            return itemList;
        },
    });
});
PK]"��views/api-user/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/api-user/list', ['views/list'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);
        },

        getCreateAttributes: function () {
            return {
                type: 'api',
            };
        },
    });
});
PK]��`U��&views/layout-set/fields/layout-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/layout-set/fields/layout-list', [
    'views/fields/multi-enum', 'views/admin/layouts/index'], function (Dep, LayoutsIndex) {

    return Dep.extend({

        typeList: [
            'list',
            'detail',
            'listSmall',
            'detailSmall',
            'bottomPanelsDetail',
            'filters',
            'massUpdate',
            'sidePanelsDetail',
            'sidePanelsEdit',
            'sidePanelsDetailSmall',
            'sidePanelsEditSmall',
        ],

        setupOptions: function () {
            this.params.options = [];
            this.translatedOptions = {};

            this.scopeList = Object.keys(this.getMetadata().get('scopes'))
                .filter(item => {
                    return this.getMetadata().get(['scopes', item, 'layouts']);
                })
                .sort((v1, v2) => {
                    return this.translate(v1, 'scopeNames')
                        .localeCompare(this.translate(v2, 'scopeNames'));
                });

            let dataList = LayoutsIndex.prototype.getLayoutScopeDataList.call(this);

            dataList.forEach(item1 => {
                item1.typeList.forEach(type => {
                    let item = item1.scope + '.' + type;

                    if (type.substr(-6) === 'Portal') {
                        return;
                    }

                    this.params.options.push(item);

                    this.translatedOptions[item] = this.translate(item1.scope, 'scopeNames') + '.' +
                        this.translate(type, 'layouts', 'Admin');
                });
            });
        },

        translateLayoutName: function (type, scope) {
            return LayoutsIndex.prototype.translateLayoutName.call(this, type, scope);
        },
    });
});
PK] H#���views/layout-set/fields/edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/layout-set/fields/edit', ['views/fields/base'], function (Dep) {

    return Dep.extend({

        detailTemplateContent:
            "<a class=\"btn btn-default\" href=\"#LayoutSet/editLayouts/id={{model.id}}\">" +
            "{{translate 'Edit Layouts' scope='LayoutSet'}}</a>",

        editTemplateContent: '',

    });
});
PK]�fqCCviews/layout-set/layouts.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import LayoutIndexView from 'views/admin/layouts/index';

class LayoutsView extends LayoutIndexView {

    setup() {
        let setId = this.setId = this.options.layoutSetId;
        this.baseUrl = '#LayoutSet/editLayouts/id=' + setId;

        super.setup();

        this.wait(
            this.getModelFactory()
                .create('LayoutSet')
                .then(m => {
                    this.sModel = m;
                    m.id = setId;

                    return m.fetch();
                })
        );
    }

    getLayoutScopeDataList() {
        let dataList = [];
        let list = this.sModel.get('layoutList') || [];

        let scopeList = [];

        list.forEach(item => {
            let arr = item.split('.');
            let scope = arr[0];

            if (~scopeList.indexOf(scope)) {
                return;
            }

            scopeList.push(scope);
        });

        scopeList.forEach(scope => {
            let o = {};

            o.scope = scope;
            o.url = this.baseUrl + '&scope=' + scope;
            o.typeDataList = [];

            let typeList = [];

            list.forEach(item => {
                let [scope, type] = item.split('.');

                if (scope !== o.scope) {
                    return;
                }

                typeList.push(type);
            });

            typeList.forEach(type => {
                o.typeDataList.push({
                    type: type,
                    url: this.baseUrl + '&scope=' + scope + '&type=' + type,
                    label: this.translateLayoutName(type, scope),
                });
            });

            o.typeList = typeList;

            dataList.push(o);
        });

        return dataList;
    }

    getHeaderHtml() {
        const separatorHtml = ' <span class="breadcrumb-separator"><span class="chevron-right"></span></span> ';

        return $('<span>')
            .append(
                $('<a>')
                    .attr('href', '#LayoutSet')
                    .text(this.translate('LayoutSet', 'scopeNamesPlural')),
                separatorHtml,
                $('<a>')
                    .attr('href', '#LayoutSet/view/' + this.sModel.id)
                    .text(this.sModel.get('name')),
                separatorHtml,
                $('<span>')
                    .text(this.translate('Edit Layouts', 'labels', 'LayoutSet'))
            )
            .get(0).outerHTML;
    }

    navigate(scope, type) {
        let url = '#LayoutSet/editLayouts/id=' + this.setId + '&scope=' + scope + '&type=' + type;

        this.getRouter().navigate(url, {trigger: false});
    }
}

export default LayoutsView;
PK]��[�99views/layout-set/record/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/layout-set/record/list', ['views/record/list'], function (Dep) {

    return Dep.extend({

        massActionList: [
            'remove',
            'export',
        ],
    });
});
PK](g3ֱ�views/external-account/index.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/external-account/index', ['view'], function (Dep) {

    return Dep.extend({

        template: 'external-account/index',

        data: function () {
            return {
                externalAccountList: this.externalAccountList,
                id: this.id,
                externalAccountListCount: this.externalAccountList.length
            };
        },

        events: {
            'click #external-account-menu a.external-account-link': function (e) {
                var id = $(e.currentTarget).data('id') + '__' + this.userId;
                this.openExternalAccount(id);
            },
        },

        setup: function () {
            this.externalAccountList = this.collection.models.map(model => model.getClonedAttributes());

            this.userId = this.getUser().id;
            this.id = this.options.id || null;

            if (this.id) {
                this.userId = this.id.split('__')[1];
            }

            this.on('after:render', function () {
                this.renderHeader();

                if (!this.id) {
                    this.renderDefaultPage();
                } else {
                    this.openExternalAccount(this.id);
                }
            });
        },

        openExternalAccount: function (id) {
            this.id = id;

            var integration = this.integration = id.split('__')[0];
            this.userId = id.split('__')[1];

            this.getRouter().navigate('#ExternalAccount/edit/' + id, {trigger: false});

            var authMethod = this.getMetadata().get(['integrations', integration, 'authMethod']);

            var viewName =
                    this.getMetadata().get(['integrations', integration, 'userView']) ||
                    'views/external-account/' + Espo.Utils.camelCaseToHyphen(authMethod);

            Espo.Ui.notify(' ... ');

            this.createView('content', viewName, {
                fullSelector: '#external-account-content',
                id: id,
                integration: integration
            }, view => {
                this.renderHeader();
                view.render();
                Espo.Ui.notify(false);

                $(window).scrollTop(0);
            });
        },

        renderDefaultPage: function () {
            $('#external-account-header').html('').hide();
            $('#external-account-content').html('');
        },

        renderHeader: function () {
            if (!this.id) {
                $('#external-account-header').html('');
                return;
            }

            $('#external-account-header').show().html(this.integration);
        },

        updatePageTitle: function () {
            this.setPageTitle(this.translate('ExternalAccount', 'scopeNamesPlural'));
        },
    });
});
PK]��d�*�* views/external-account/oauth2.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/external-account/oauth2', ['view', 'model'], function (Dep, Model) {

    return Dep.extend({

        template: 'external-account/oauth2',

        data: function () {
            return {
                integration: this.integration,
                helpText: this.helpText,
                isConnected: this.isConnected,
            };
        },

        isConnected: false,

        events: {
            'click button[data-action="cancel"]': function () {
                this.getRouter().navigate('#ExternalAccount', {trigger: true});
            },
            'click button[data-action="save"]': function () {
                this.save();
            },
            'click [data-action="connect"]': function () {
                this.connect();
            }
        },

        setup: function () {
            this.integration = this.options.integration;
            this.id = this.options.id;

            this.helpText = false;

            if (this.getLanguage().has(this.integration, 'help', 'ExternalAccount')) {
                this.helpText = this.translate(this.integration, 'help', 'ExternalAccount');
            }

            this.fieldList = [];

            this.dataFieldList = [];

            this.model = new Model();
            this.model.id = this.id;
            this.model.entityType = this.model.name = 'ExternalAccount';
            this.model.urlRoot = 'ExternalAccount';

            this.model.defs = {
                fields: {
                    enabled: {
                        required: true,
                        type: 'bool'
                    },
                }
            };

            this.wait(true);

            this.model.populateDefaults();

            this.listenToOnce(this.model, 'sync', () => {
                this.createFieldView('bool', 'enabled');

                Espo.Ajax.getRequest('ExternalAccount/action/getOAuth2Info?id=' + this.id)
                    .then(response => {
                        this.clientId = response.clientId;
                        this.redirectUri = response.redirectUri;

                        if (response.isConnected) {
                            this.isConnected = true;
                        }

                        this.wait(false);
                    });
            });

            this.model.fetch();
        },

        hideField: function (name) {
            this.$el.find('label[data-name="'+name+'"]').addClass('hide');
            this.$el.find('div.field[data-name="'+name+'"]').addClass('hide');

            var view = this.getView(name);

            if (view) {
                view.disabled = true;
            }
        },

        showField: function (name) {
            this.$el.find('label[data-name="'+name+'"]').removeClass('hide');
            this.$el.find('div.field[data-name="'+name+'"]').removeClass('hide');

            var view = this.getView(name);

            if (view) {
                view.disabled = false;
            }
        },

        afterRender: function () {
            if (!this.model.get('enabled')) {
                this.$el.find('.data-panel').addClass('hidden');
            }

            this.listenTo(this.model, 'change:enabled', () => {
                if (this.model.get('enabled')) {
                    this.$el.find('.data-panel').removeClass('hidden');
                } else {
                    this.$el.find('.data-panel').addClass('hidden');
                }
            });
        },

        createFieldView: function (type, name, readOnly, params) {
            this.createView(name, this.getFieldManager().getViewName(type), {
                model: this.model,
                selector: '.field[data-name="' + name + '"]',
                defs: {
                    name: name,
                    params: params
                },
                mode: readOnly ? 'detail' : 'edit',
                readOnly: readOnly,
            });

            this.fieldList.push(name);
        },

        save: function () {
            this.fieldList.forEach(field => {
                var view = this.getView(field);

                if (!view.readOnly) {
                    view.fetchToModel();
                }
            });

            var notValid = false;

            this.fieldList.forEach((field) => {
                notValid = this.getView(field).validate() || notValid;
            });

            if (notValid) {
                this.notify('Not valid', 'error');
                return;
            }

            this.listenToOnce(this.model, 'sync', () => {
                this.notify('Saved', 'success');

                if (!this.model.get('enabled')) {
                    this.setNotConnected();
                }
            });

            Espo.Ui.notify(this.translate('saving', 'messages'));

            this.model.save();
        },

        popup: function (options, callback) {
            options.windowName = options.windowName ||  'ConnectWithOAuth';
            options.windowOptions = options.windowOptions || 'location=0,status=0,width=800,height=400';
            options.callback = options.callback || function(){ window.location.reload(); };

            var self = this;

            var path = options.path;

            var arr = [];
            var params = (options.params || {});

            for (var name in params) {
                if (params[name]) {
                    arr.push(name + '=' + encodeURI(params[name]));
                }
            }
            path += '?' + arr.join('&');

            var parseUrl = function (str) {
                var code = null;
                var error = null;

                str = str.substr(str.indexOf('?') + 1, str.length);

                str.split('&').forEach((part) => {
                    var arr = part.split('=');
                    var name = decodeURI(arr[0]);
                    var value = decodeURI(arr[1] || '');

                    if (name === 'code') {
                        code = value;
                    }

                    if (name === 'error') {
                        error = value;
                    }
                });

                if (code) {
                    return {
                        code: code,
                    };
                } else if (error) {
                    return {
                        error: error,
                    };
                }
            }

            let popup = window.open(path, options.windowName, options.windowOptions);

            let interval;

            interval = window.setInterval(() => {
                if (popup.closed) {
                    window.clearInterval(interval);
                } else {
                    var res = parseUrl(popup.location.href.toString());

                    if (res) {
                        callback.call(self, res);
                        popup.close();
                        window.clearInterval(interval);
                    }
                }
            }, 500);
        },

        connect: function () {
            this.popup({
                path: this.getMetadata().get('integrations.' + this.integration + '.params.endpoint'),
                params: {
                    client_id: this.clientId,
                    redirect_uri: this.redirectUri,
                    scope: this.getMetadata().get('integrations.' + this.integration + '.params.scope'),
                    response_type: 'code',
                    access_type: 'offline',
                    approval_prompt: 'force',
                }
            }, function (res) {
                if (res.error) {
                    Espo.Ui.notify(false);

                    return;
                }

                if (res.code) {
                    this.$el.find('[data-action="connect"]').addClass('disabled');

                    Espo.Ajax
                        .postRequest('ExternalAccount/action/authorizationCode', {
                            id: this.id,
                            code: res.code,
                        })
                        .then(response => {
                            Espo.Ui.notify(false);

                            if (response === true) {
                                this.setConnected();
                            } else {
                                this.setNotConneted();
                            }

                            this.$el.find('[data-action="connect"]').removeClass('disabled');
                        })
                        .catch(() => {
                            this.$el.find('[data-action="connect"]').removeClass('disabled');
                        });
                } else {
                    this.notify('Error occurred', 'error');
                }
            });
        },

        setConnected: function () {
            this.isConnected = true;

            this.$el.find('[data-action="connect"]').addClass('hidden');;
            this.$el.find('.connected-label').removeClass('hidden');
        },

        setNotConnected: function () {
            this.isConnected = false;

            this.$el.find('[data-action="connect"]').removeClass('hidden');;
            this.$el.find('.connected-label').addClass('hidden');
        },
    });
});
PK]g�V���&views/templates/event/record/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/templates/event/record/detail', ['views/record/detail'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            if (this.getAcl().checkModel(this.model, 'edit')) {
                if (['Held', 'Not Held'].indexOf(this.model.get('status')) === -1) {
                    this.dropdownItemList.push({
                        'html': this.translate('Set Held', 'labels', this.scope),
                        'name': 'setHeld',
                    });

                    this.dropdownItemList.push({
                        'html': this.translate('Set Not Held', 'labels', this.scope),
                        'name': 'setNotHeld',
                    });
                }
            }
        },

        actionSetHeld: function () {
            this.model
                .save({status: 'Held'}, {patch: true})
                .then(() => {
                    Espo.Ui.success(this.translate('Saved', 'labels', 'Meeting'));

                    this.removeButton('setHeld');
                    this.removeButton('setNotHeld');
                });
        },

        actionSetNotHeld: function () {
            this.model
                .save({status: 'Not Held'}, {patch: true})
                .then(() => {
                    Espo.Ui.success(this.translate('Saved', 'labels', 'Meeting'));

                    this.removeButton('setHeld');
                    this.removeButton('setNotHeld');
                });
        },
    });
});
PK]�?��WNWNviews/dashboard.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/dashboard */

import View from 'view';
import GridStack from 'gridstack';

class DashboardView extends View {

    template = 'dashboard'

    dashboardLayout = null
    currentTab = null

    WIDTH_MULTIPLIER = 3

    events = {
        /** @this DashboardView */
        'click button[data-action="selectTab"]': function (e) {
            let tab = parseInt($(e.currentTarget).data('tab'));

            this.selectTab(tab);
        },
        /** @this DashboardView */
        'click .dashboard-buttons [data-action="addDashlet"]': function () {
            this.createView('addDashlet', 'views/modals/add-dashlet', {}, view => {
                view.render();

                this.listenToOnce(view, 'add', name => {
                    this.addDashlet(name);
                });
            });
        },
        /** @this DashboardView */
        'click .dashboard-buttons [data-action="editTabs"]': function () {
            this.editTabs();
        },
    }

    data() {
        return {
            displayTitle: this.options.displayTitle,
            currentTab: this.currentTab,
            tabCount: this.dashboardLayout.length,
            dashboardLayout: this.dashboardLayout,
            layoutReadOnly: this.layoutReadOnly,
            hasAdd: !this.layoutReadOnly && !this.getPreferences().get('dashboardLocked'),
        };
    }

    generateId() {
        return (Math.floor(Math.random() * 10000001)).toString();
    }

    setupCurrentTabLayout() {
        if (!this.dashboardLayout) {
            let defaultLayout = [
                {
                    "name": "My Espo",
                    "layout": [],
                }
            ];

            if (this.getConfig().get('forcedDashboardLayout')) {
                this.dashboardLayout = this.getConfig().get('forcedDashboardLayout') || [];
            }
            else if (this.getUser().get('portalId')) {
                this.dashboardLayout = this.getConfig().get('dashboardLayout') || [];
            }
            else {
                this.dashboardLayout = this.getPreferences().get('dashboardLayout') || defaultLayout;
            }

            if (
                this.dashboardLayout.length === 0 ||
                Object.prototype.toString.call(this.dashboardLayout) !== '[object Array]'
            ) {
                this.dashboardLayout = defaultLayout;
            }
        }

        let dashboardLayout = this.dashboardLayout || [];

        if (dashboardLayout.length <= this.currentTab) {
            this.currentTab = 0;
        }

        let tabLayout = dashboardLayout[this.currentTab].layout || [];

        tabLayout = GridStack.Utils.sort(tabLayout);

        this.currentTabLayout = tabLayout;
    }

    storeCurrentTab(tab) {
        this.getStorage().set('state', 'dashboardTab', tab);
    }

    selectTab(tab) {
        this.$el.find('.page-header button[data-action="selectTab"]').removeClass('active');
        this.$el.find('.page-header button[data-action="selectTab"][data-tab="'+tab+'"]').addClass('active');

        this.currentTab = tab;
        this.storeCurrentTab(tab);

        this.setupCurrentTabLayout();

        this.dashletIdList.forEach(id => {
            this.clearView('dashlet-'+id);
        });

        this.dashletIdList = [];

        this.reRender();
    }

    setup() {
        this.currentTab = this.getStorage().get('state', 'dashboardTab') || 0;
        this.setupCurrentTabLayout();

        this.dashletIdList = [];

        this.screenWidthXs = this.getThemeManager().getParam('screenWidthXs');

        if (this.getUser().isPortal()) {
            this.layoutReadOnly = true;
            this.dashletsReadOnly = true;
        }
        else {
            let forbiddenPreferencesFieldList = this.getAcl()
                .getScopeForbiddenFieldList('Preferences', 'edit');

            if (~forbiddenPreferencesFieldList.indexOf('dashboardLayout')) {
                this.layoutReadOnly = true;
            }

            if (~forbiddenPreferencesFieldList.indexOf('dashletsOptions')) {
                this.dashletsReadOnly = true;
            }
        }

        this.once('remove', () => {
            if (this.grid) {
                this.grid.destroy();
            }

            if (this.fallbackModeTimeout) {
                clearTimeout(this.fallbackModeTimeout);
            }

            $(window).off('resize.dashboard');
        });
    }

    afterRender() {
        this.$dashboard = this.$el.find('> .dashlets');

        if (window.innerWidth >= this.screenWidthXs) {
            this.initGridstack();
        }
        else {
            this.initFallbackMode();
        }

        $(window).off('resize.dashboard');
        $(window).on('resize.dashboard', this.onResize.bind(this));
    }

    onResize() {
        if (this.isFallbackMode() && window.innerWidth >= this.screenWidthXs) {
            this.initGridstack();
        }
        else if (!this.isFallbackMode() && window.innerWidth < this.screenWidthXs) {
            this.initFallbackMode();
        }
    }

    isFallbackMode() {
        return this.$dashboard.hasClass('fallback');
    }

    preserveDashletViews() {
        this.preservedDashletViews = {};
        this.preservedDashletElements = {};

        this.currentTabLayout.forEach(o => {
            let key = 'dashlet-' + o.id;
            let view = this.getView(key);

            this.unchainView(key);

            this.preservedDashletViews[o.id] = view;

            let $el = view.$el.children(0);

            this.preservedDashletElements[o.id] = $el;

            $el.detach();
        });
    }

    addPreservedDashlet(id) {
        let view = this.preservedDashletViews[id];
        let $el = this.preservedDashletElements[id];

        this.$el.find('.dashlet-container[data-id="'+id+'"]').append($el);

        this.setView('dashlet-' + id, view);
    }

    clearPreservedDashlets() {
        this.preservedDashletViews = null;
        this.preservedDashletElements = null;
    }

    hasPreservedDashlets() {
        return !!this.preservedDashletViews;
    }

    initFallbackMode() {
        if (this.grid) {
            this.grid.destroy(false);
            this.grid = null;

            this.preserveDashletViews();
        }

        this.$dashboard.empty();

        let $dashboard = this.$dashboard;

        $dashboard.addClass('fallback');

        this.currentTabLayout.forEach(o => {
            let $item = this.prepareFallbackItem(o);

            $dashboard.append($item);
        });

        this.currentTabLayout.forEach(o => {
            if (!o.id || !o.name) {
                return;
            }

            if (!this.getMetadata().get(['dashlets', o.name])) {
                console.error("Dashlet " + o.name + " doesn't exist or not available.");

                return;
            }

            if (this.hasPreservedDashlets()) {
                this.addPreservedDashlet(o.id);

                return;
            }

            this.createDashletView(o.id, o.name);
        });

        this.clearPreservedDashlets();

        if (this.fallbackModeTimeout) {
            clearTimeout(this.fallbackModeTimeout);
        }

        this.$dashboard.css('height', '');

        this.fallbackControlHeights();
    }

    fallbackControlHeights() {
        this.currentTabLayout.forEach(o => {
            let $container = this.$dashboard.find('.dashlet-container[data-id="'+o.id+'"]');

            let headerHeight = $container.find('.panel-heading').outerHeight();

            let $body = $container.find('.dashlet-body');

            let bodyEl = $body.get(0);

            if (!bodyEl) {
                return;
            }

            if (bodyEl.scrollHeight > bodyEl.offsetHeight) {
                let height = bodyEl.scrollHeight + headerHeight;

                $container.css('height', height + 'px');
            }
        });

        this.fallbackModeTimeout = setTimeout(() => {
            this.fallbackControlHeights();
        }, 300);
    }

    initGridstack() {
        if (this.isFallbackMode()) {
            this.preserveDashletViews();
        }

        this.$dashboard.empty();

        let $gridstack = this.$gridstack = this.$dashboard;

        $gridstack.removeClass('fallback');

        if (this.fallbackModeTimeout) {
            clearTimeout(this.fallbackModeTimeout);
        }

        let disableDrag = false;
        let disableResize = false;

        if (this.getUser().isPortal() || this.getPreferences().get('dashboardLocked')) {
            disableDrag = true;
            disableResize = true;
        }

        let grid = this.grid = GridStack.init(
            {
                cellHeight: this.getThemeManager().getParam('dashboardCellHeight') * 1.14,
                margin: this.getThemeManager().getParam('dashboardCellMargin') / 2,
                column: 12,
                handle: '.panel-heading',
                disableDrag: disableDrag,
                disableResize: disableResize,
                disableOneColumnMode: true,
                draggable: {
                    distance: 10,
                },
                dragInOptions: {
                    scroll: false,
                },
                float: false,
                animate: false,
                scroll: false,
            },
            $gridstack.get(0)
        );

        grid.removeAll();

        this.currentTabLayout.forEach(o => {
            let $item = this.prepareGridstackItem(o.id, o.name);

            if (!this.getMetadata().get(['dashlets', o.name])) {
                return;
            }

            grid.addWidget(
                $item.get(0),
                {
                    x: o.x * this.WIDTH_MULTIPLIER,
                    y: o.y,
                    w: o.width * this.WIDTH_MULTIPLIER,
                    h: o.height,
                }
            );
        });

        $gridstack.find('.grid-stack-item').css('position', 'absolute');

        this.currentTabLayout.forEach(o => {
            if (!o.id || !o.name) {
                return;
            }

            if (!this.getMetadata().get(['dashlets', o.name])) {
                console.error("Dashlet " + o.name + " doesn't exist or not available.");

                return;
            }

            if (this.hasPreservedDashlets()) {
                this.addPreservedDashlet(o.id);

                return;
            }

            this.createDashletView(o.id, o.name);
        });

        this.clearPreservedDashlets();

        this.grid.on('change', () => {
            this.fetchLayout();
            this.saveLayout();
        });

        // noinspection SpellCheckingInspection
        this.grid.on('resizestop', e => {
            let id = $(e.target).data('id');
            let view = this.getView('dashlet-' + id);

            if (!view) {
                return;
            }
            view.trigger('resize');
        });
    }

    fetchLayout() {
        this.dashboardLayout[this.currentTab].layout =
            _.map(this.$gridstack.find('.grid-stack-item'), el => {
                let $el = $(el);

                let x = $el.attr('gs-x');
                let y = $el.attr('gs-y');
                let h = $el.attr('gs-h');
                let w = $el.attr('gs-w');

                return {
                    id: $el.data('id'),
                    name: $el.data('name'),
                    x: x / this.WIDTH_MULTIPLIER,
                    y: y,
                    width: w / this.WIDTH_MULTIPLIER,
                    height: h,
                };
            });
    }

    prepareGridstackItem(id, name) {
        let $item = $('<div>').addClass('grid-stack-item');
        let $container = $('<div class="grid-stack-item-content dashlet-container"></div>');

        $container.attr('data-id', id);
        $container.attr('data-name', name);

        $item.attr('data-id', id);
        $item.attr('data-name', name);

        $item.append($container);

        return $item;
    }

    prepareFallbackItem(o) {
        let $item = $('<div>');
        let $container = $('<div class="dashlet-container">');

        $container.attr('data-id', o.id);
        $container.attr('data-name', o.name);
        $container.attr('data-x', o.x);
        $container.attr('data-y', o.y);
        $container.attr('data-height', o.height);
        $container.attr('data-width', o.width);
        $container.css('height', (o.height *
            this.getThemeManager().getParam('dashboardCellHeight')) + 'px');

        $item.attr('data-id', o.id);
        $item.attr('data-name', o.name);

        $item.append($container);

        return $item;
    }

    saveLayout(attributes) {
        if (this.layoutReadOnly) {
            return;
        }

        attributes = {
            ...(attributes || {}),
            ...{dashboardLayout: this.dashboardLayout},
        };

        this.getPreferences().save(attributes, {patch: true});

        this.getPreferences().trigger('update');
    }

    removeDashlet(id) {
        let revertToFallback = false;

        if (this.isFallbackMode()) {
            this.initGridstack();

            revertToFallback = true;
        }

        let $item = this.$gridstack.find('.grid-stack-item[data-id="'+id+'"]');

        // noinspection JSUnresolvedReference
        this.grid.removeWidget($item.get(0), true);

        let layout = this.dashboardLayout[this.currentTab].layout;

        layout.forEach((o, i) => {
            if (o.id === id) {
                layout.splice(i, 1);
            }
        });

        let o = {};

        o.dashletsOptions = this.getPreferences().get('dashletsOptions') || {};

        delete o.dashletsOptions[id];

        o.dashboardLayout = this.dashboardLayout;

        if (this.layoutReadOnly) {
            return;
        }

        this.getPreferences().save(o, {patch: true});
        this.getPreferences().trigger('update');

        let index = this.dashletIdList.indexOf(id);

        if (~index) {
            this.dashletIdList.splice(index, index);
        }

        this.clearView('dashlet-' + id);

        this.setupCurrentTabLayout();

        if (revertToFallback) {
            this.initFallbackMode();
        }
    }

    addDashlet(name) {
        let revertToFallback = false;

        if (this.isFallbackMode()) {
            this.initGridstack();

            revertToFallback = true;
        }

        let id = 'd' + (Math.floor(Math.random() * 1000001)).toString();

        let $item = this.prepareGridstackItem(id, name);

        this.grid.addWidget(
            $item.get(0),
            {
                x: 0,
                y: 0,
                w: 2 * this.WIDTH_MULTIPLIER,
                h: 2,
            }
        );

        this.createDashletView(id, name, name, view => {
            this.fetchLayout();
            this.saveLayout();

            this.setupCurrentTabLayout();

            if (view.getView('body') && view.getView('body').afterAdding) {
                view.getView('body').afterAdding.call(view.getView('body'));
            }

            if (revertToFallback) {
                this.initFallbackMode();
            }
        });
    }

    createDashletView(id, name, label, callback) {
        let o = {
            id: id,
            name: name,
        };

        if (label) {
            o.label = label;
        }

        return this.createView('dashlet-' + id, 'views/dashlet', {
            label: name,
            name: name,
            id: id,
            selector: '> .dashlets .dashlet-container[data-id="' + id + '"]',
            readOnly: this.dashletsReadOnly,
            locked: this.getPreferences().get('dashboardLocked'),
        }, view => {
            this.dashletIdList.push(id);

            view.render();

            this.listenToOnce(view, 'change', () => {
                this.clearView(id);

                this.createDashletView(id, name, label);
            });

            this.listenToOnce(view, 'remove-dashlet', () => {
                this.removeDashlet(id);
            });

            if (callback) {
                callback.call(this, view);
            }
        });
    }

    editTabs() {
        let dashboardLocked = this.getPreferences().get('dashboardLocked');

        this.createView('editTabs', 'views/modals/edit-dashboard', {
            dashboardLayout: this.dashboardLayout,
            dashboardLocked: dashboardLocked,
            fromDashboard: true,
        }, view => {
            view.render();

            this.listenToOnce(view, 'after:save', data => {
                view.close();

                let dashboardLayout = [];

                data.dashboardTabList.forEach(name => {
                    let layout = [];
                    let id = null;

                    this.dashboardLayout.forEach(d => {
                        if (d.name === name) {
                            layout = d.layout;
                            id = d.id;
                        }
                    });

                    if (name in data.renameMap) {
                        name = data.renameMap[name];
                    }

                    let o = {
                        name: name,
                        layout: layout,
                    };

                    if (id) {
                        o.id = id;
                    }

                    dashboardLayout.push(o);
                });

                this.dashletIdList.forEach(item => {
                    this.clearView('dashlet-' + item);
                });

                this.dashboardLayout = dashboardLayout;

                this.saveLayout({
                    dashboardLocked: data.dashboardLocked,
                });

                this.storeCurrentTab(0);
                this.currentTab = 0;
                this.setupCurrentTabLayout();

                this.reRender();
            });
        });
    }
}

export default DashboardView;
PK]v^��

!views/attachment/fields/parent.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of tтhe "EspoCRM" word.
 ************************************************************************/

define('views/attachment/fields/parent', ['views/fields/link-parent'], function (Dep) {

    return Dep.extend({

        ignoreScopeList: [
            'Preferences',
            'ExternalAccount',
            'Notification',
            'Note',
            'ArrayValue',
            'Attachment',
        ],

        displayEntityType: true,

        setup: function () {
            Dep.prototype.setup.call(this);

            this.foreignScopeList = this.getMetadata().getScopeEntityList().filter(item => {
                if (!this.getUser().isAdmin()) {
                    if (!this.getAcl().checkScopeHasAcl(item)) {
                        return;
                    }
                }

                if (~this.ignoreScopeList.indexOf(item)) {
                    return;
                }

                if (!this.getAcl().checkScope(item)) {
                    return;
                }

                return true;
            });

            this.getLanguage().sortEntityList(this.foreignScopeList);

            this.foreignScope = this.model.get(this.typeName) || this.foreignScopeList[0];
        },
    });
});
PK].d���views/attachment/fields/name.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of tтhe "EspoCRM" word.
 ************************************************************************/

define('views/attachment/fields/name', ['views/fields/varchar'], function (Dep) {

    return Dep.extend({

        detailTemplate: 'attachment/fields/name/detail',

        data: function () {
            var data = Dep.prototype.data.call(this);

            var url = this.getBasePath() + '?entryPoint=download&id=' + this.model.id;

            if (this.getUser().get('portalId')) {
                url += '&portalId=' + this.getUser().get('portalId');
            }

            data.url = url;

            return data;
        },
    });
});
PK]���!views/attachment/record/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/attachment/record/detail', ['views/record/detail'], function (Dep) {

    return Dep.extend({

        readOnly: true,
    });
});
PK]lt�NNviews/attachment/record/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/attachment/record/list', ['views/record/list'], function (Dep) {

    return Dep.extend({

        rowActionsView: 'views/record/row-actions/view-and-remove',
        massActionList: ['remove'],
    });
});
PK]e�_K��%views/attachment/modals/select-one.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/attachment/modals/select-one', ['views/modal'], function (Dep) {

    return Dep.extend({

        backdrop: true,

        // language=Handlebars
        templateContent:
            '<ul class="list-group no-side-margin">{{#each viewObject.options.dataList}}'+
            '<li class="list-group-item">'+
            '<a role="button" class="action" data-action="select" data-id="{{id}}">{{name}}</a>'+
            '</li>'+
            '{{/each}}</ul>',

        setup: function () {
            this.headerText = this.translate('Select');

            if (this.options.fieldLabel) {
                this.headerText += ': ' + this.options.fieldLabel;
            }
        },

        actionSelect: function (data) {
            this.trigger('select', data.id);
            this.remove();
        },
    });
});
PK]��w�!views/attachment/modals/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/attachment/modals/detail', ['views/modals/detail'], function (Dep) {

    return Dep.extend({

        editDisabled: true,
    });
});
PK]��T::views/site/master.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/site/master */

import View from 'view';
import $ from 'jquery';

class MasterSiteView extends View {

    template = 'site/master'

    views = {
        header: {
            id: 'header',
            view: 'views/site/header',
        },
        main: {
            id: 'main',
            view: false,
        },
        footer: {
            fullSelector: 'body > footer',
            view: 'views/site/footer',
        },
    }

    showLoadingNotification() {
        Espo.Ui.notify(' ... ');
    }

    hideLoadingNotification() {
        Espo.Ui.notify(false);
    }

    setup() {
        $(window).on('resize.' + this.cid, () => {
            this.adjustContent();
        });
    }

    onRemove() {
        $(window).off('resize.' + this.cid);
    }

    afterRender() {
        let params = this.getThemeManager().getParam('params');

        let $body = $('body');

        for (let param in params) {
            let value = this.getThemeManager().getParam(param);

            $body.attr('data-' + Espo.Utils.camelCaseToHyphen(param), value);
        }

        let footerView = this.getView('footer');

        if (footerView) {
            let html = footerView.$el.html() || '';

            if ((html.match(/espocrm/gi) || []).length < 2) {
                let text = 'PHAgY2xhc3M9ImNyZWRpdCBzbWFsbCI+JmNvcHk7IDxhIGhyZWY9Imh0dHA6Ly93d3cuZXNwb2Nyb' +
                    'S5jb20iPkVzcG9DUk08L2E+PC9wPg==';

                let decText;

                if (typeof window.atob === "function") {
                    decText = window.atob(text);
                } else if (typeof atob === "function") {
                    decText = atob(text);
                }

                if (decText) {
                    footerView.$el.html(decText);
                }
            }
        }

        this.$content = this.$el.find('> #content');

        this.adjustContent();

        let extensions = this.getHelper().getAppParam('extensions') || [];

        if (this.getConfig().get('maintenanceMode')) {
            this.createView('dialog', 'views/modal', {
                templateContent: '<div class="text-danger">{{complexText viewObject.options.message}}</div>',
                headerText: this.translate('maintenanceMode', 'fields', 'Settings'),
                backdrop: true,
                message: this.translate('maintenanceMode', 'messages'),
                buttonList: [
                    {
                        name: 'close',
                        label: this.translate('Close'),
                    }
                ],
            }, view => {
                view.render();
            });
        }
        else if (this.getHelper().getAppParam('auth2FARequired')) {
            this.createView('dialog', 'views/modals/auth2fa-required', {}, (view) => {
                view.render();
            });
        }
        else if (extensions.length !== 0) {
            this.processExtensions(extensions);
        }
    }

    adjustContent() {
        if (!this.isRendered()) {
            return;
        }

        if (window.innerWidth < this.getThemeManager().getParam('screenWidthXs')) {
            this.isSmallScreen = true;

            let height = window.innerHeight - this.$content.get(0).getBoundingClientRect().top;

            let $navbarCollapse = $('#navbar .navbar-body');

            if ($navbarCollapse.hasClass('in') || $navbarCollapse.hasClass('collapsing')) {
                height += $navbarCollapse.height();
            }

            let footerHeight = $('#footer').height() || 26;

            height -= footerHeight;

            if (height <= 0) {
                this.$content.css('minHeight', '');

                return;
            }

            this.$content.css('minHeight', height + 'px');

            return;
        }

        if (this.isSmallScreen) {
            this.$content.css('minHeight', '');
        }

        this.isSmallScreen = false;
    }

    /**
     * @param {{
     *     name: string,
     *     licenseStatus: string,
     *     licenseStatusMessage:? string,
     *     notify: boolean,
     * }[]} list
     */
    processExtensions(list) {
        let messageList = [];

        list.forEach(item => {
            if (!item.notify) {
                return;
            }

            let message = item.licenseStatusMessage ??
                'extensionLicense' +
                Espo.Utils.upperCaseFirst(
                    Espo.Utils.hyphenToCamelCase(item.licenseStatus.toLowerCase())
                );

            messageList.push(
                this.translate(message, 'messages')
                    .replace('{name}', item.name)
            );
        });

        if (!messageList.length) {
            return;
        }

        let message = messageList.join('\n\n');

        message = this.getHelper().transformMarkdownText(message);

        let dialog = new Espo.Ui.Dialog({
            backdrop: 'static',
            buttonList: [
                {
                    name: 'close',
                    text: this.translate('Close'),
                    className: 'btn-s-wide',
                    onClick: () => dialog.close(),
                }
            ],
            className: 'dialog-confirm text-danger',
            body: message.toString(),
        });

        dialog.show();
    }
}

export default MasterSiteView;
PK]v�Qߓ�views/site/header.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';

class HeaderSiteView extends View {

    template = 'site/header'

    title = 'EspoCRM'
    navbarView = 'views/site/navbar'
    customViewPath = ['clientDefs', 'App', 'navbarView']

    data = {
        title: this.title,
    }

    setup() {
        let navbarView = this.getMetadata().get(this.customViewPath) || this.navbarView;

        this.createView('navbar', navbarView, {
            fullSelector: '#navbar',
            title: this.title,
        });
    }
}

export default HeaderSiteView;
PK]�v-Q�Q�views/site/navbar.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';
import $ from 'jquery';

class NavbarSiteView extends View {

    template = 'site/navbar'

    currentTab = null

    events = {
        /** @this NavbarSiteView */
        'click .navbar-collapse.in a.nav-link': function (e) {
            const $a = $(e.currentTarget);
            const href = $a.attr('href');

            if (href) {
                this.xsCollapse();
            }
        },
        /** @this NavbarSiteView */
        'click a.nav-link': function () {
            if (this.isSideMenuOpened) {
                this.closeSideMenu();
            }
        },
        /** @this NavbarSiteView */
        'click a.navbar-brand.nav-link': function () {
            this.xsCollapse();
        },
        /** @this NavbarSiteView */
        'click a[data-action="quick-create"]': function (e) {
            e.preventDefault();

            const scope = $(e.currentTarget).data('name');

            this.quickCreate(scope);
        },
        /** @this NavbarSiteView */
        'click a.minimizer': function () {
            this.switchMinimizer();
        },
        /** @this NavbarSiteView */
        'click a.side-menu-button': function () {
            this.switchSideMenu();
        },
        /** @this NavbarSiteView */
        'click a.action': function (e) {
            Espo.Utils.handleAction(this, e.originalEvent, e.currentTarget);
        },
        /** @this NavbarSiteView */
        'click [data-action="toggleCollapsable"]': function () {
            this.toggleCollapsable();
        },
        /** @this NavbarSiteView */
        'click li.show-more a': function (e) {
            e.stopPropagation();
            this.showMoreTabs();
        },
        /** @this NavbarSiteView */
        'click .not-in-more > .nav-link-group': function (e) {
            this.handleGroupDropdownClick(e);
        },
        /** @this NavbarSiteView */
        'click .in-more .nav-link-group': function (e) {
            this.handleGroupDropdownClick(e);
        },
    }

    data() {
        return {
            tabDefsList1: this.tabDefsList.filter(item => !item.isInMore),
            tabDefsList2: this.tabDefsList.filter(item => item.isInMore),
            title: this.options.title,
            menuDataList: this.getMenuDataList(),
            quickCreateList: this.quickCreateList,
            enableQuickCreate: this.quickCreateList.length > 0,
            userId: this.getUser().id,
            logoSrc: this.getLogoSrc(),
        };
    }

    handleGroupDropdownClick(e) {
        const $target = $(e.currentTarget).parent();

        if ($target.parent().hasClass('more-dropdown-menu')) {
            e.stopPropagation();

            if ($target.hasClass('open')) {
                $target.removeClass('open');

                return;
            }

            this.handleGroupDropdownInMoreOpen($target);

            return;
        }

        if ($target.hasClass('open')) {
            return;
        }

        this.handleGroupDropdownOpen($target);
    }

    handleGroupMenuPosition($menu, $target) {
        if (this.navbarAdjustmentHandler && this.navbarAdjustmentHandler.handleGroupMenuPosition()) {
            this.handleGroupMenuPosition($menu, $target);

            return;
        }

        const rectItem = $target.get(0).getBoundingClientRect();

        const windowHeight = window.innerHeight;

        const isSide = this.isSide();

        if (
            !isSide &&
            !$target.parent().hasClass('more-dropdown-menu')
        ) {
            let maxHeight = windowHeight - rectItem.bottom;

            this.handleGroupMenuScrolling($menu, $target, maxHeight);

            return;
        }

        const itemCount = $menu.children().length;

        const tabHeight = isSide ?
            this.$tabs.find('> .tab:not(.tab-divider)').height() :
            this.$tabs.find('.tab-group > ul > li:visible').height();

        const menuHeight = tabHeight * itemCount;

        let top = rectItem.top - 1;

        if (top + menuHeight > windowHeight) {
            top = windowHeight - menuHeight - 2;

            if (top < 0) {
                top = 0;
            }
        }

        $menu.css({top: top + 'px'});

        const maxHeight = windowHeight - top;

        this.handleGroupMenuScrolling($menu, $target, maxHeight);
    }

    handleGroupMenuScrolling($menu, $target, maxHeight) {
        $menu.css({
            maxHeight: maxHeight + 'px',
        });

        const $window = $(window);

        $window.off('scroll.navbar-tab-group');

        $window.on('scroll.navbar-tab-group', () => {
            if (!$menu.get(0) || !$target.get(0)) {
                return;
            }

            if (!$target.hasClass('open')) {
                return;
            }

            $menu.scrollTop($window.scrollTop());
        });
    }

    handleGroupDropdownOpen($target) {
        const $menu = $target.find('.dropdown-menu');

        this.handleGroupMenuPosition($menu, $target);

        setTimeout(() => {
            this.adjustBodyMinHeight();
        }, 50);

        $target.off('hidden.bs.dropdown');

        $target.on('hidden.bs.dropdown', () => {
            this.adjustBodyMinHeight();
        });
    }

    handleGroupDropdownInMoreOpen($target) {
        this.$el.find('.tab-group.tab.dropdown').removeClass('open');

        const $parentDropdown = this.$el.find('.more-dropdown-menu');

        $target.addClass('open');

        const $menu = $target.find('.dropdown-menu');

        const rectDropdown = $parentDropdown.get(0).getBoundingClientRect();

        const left = rectDropdown.right;

        $menu.css({
            left: left + 'px',
        });

        this.handleGroupMenuPosition($menu, $target);

        this.adjustBodyMinHeight();

        if (!this.isSide()) {
            if (left + $menu.width() > window.innerWidth) {
                $menu.css({
                    left: rectDropdown.left - $menu.width() - 2,
                });
            }
        }
    }

    isCollapsableVisible() {
        return this.$el.find('.navbar-body').hasClass('in');
    }

    toggleCollapsable() {
        if (this.isCollapsableVisible()) {
            this.hideCollapsable();
        } else {
            this.showCollapsable();
        }
    }

    hideCollapsable() {
        this.$el.find('.navbar-body').removeClass('in');
    }

    showCollapsable() {
        this.$el.find('.navbar-body').addClass('in');
    }

    xsCollapse() {
        this.hideCollapsable();
    }

    isMinimized() {
        return this.$body.hasClass('minimized');
    }

    switchSideMenu() {
        if (!this.isMinimized()) return;

        if (this.isSideMenuOpened) {
            this.closeSideMenu();
        } else {
            this.openSideMenu();
        }
    }

    openSideMenu() {
        this.isSideMenuOpened = true;

        this.$body.addClass('side-menu-opened');

        this.$sideMenuBackdrop =
            $('<div>')
                .addClass('side-menu-backdrop')
                .click(() => this.closeSideMenu())
                .appendTo(this.$body);

        this.$sideMenuBackdrop2 =
            $('<div>')
                .addClass('side-menu-backdrop')
                .click(() => this.closeSideMenu())
                .appendTo(this.$navbarRightContainer);
    }

    closeSideMenu() {
        this.isSideMenuOpened = false;
        this.$body.removeClass('side-menu-opened');
        this.$sideMenuBackdrop.remove();
        this.$sideMenuBackdrop2.remove();
    }

    switchMinimizer() {
        const $body = this.$body;

        if (this.isMinimized()) {
            if (this.isSideMenuOpened) {
                this.closeSideMenu();
            }

            $body.removeClass('minimized');

            this.getStorage().set('state', 'siteLayoutState', 'expanded');
        }
        else {
            $body.addClass('minimized');

            this.getStorage().set('state', 'siteLayoutState', 'collapsed');
        }

        if (window.Event) {
            try {
                window.dispatchEvent(new Event('resize'));
            } catch (e) {}
        }
    }

    getLogoSrc() {
        const companyLogoId = this.getConfig().get('companyLogoId');

        if (!companyLogoId) {
            return this.getBasePath() + (this.getThemeManager().getParam('logo') || 'client/img/logo.svg');
        }

        return this.getBasePath() + '?entryPoint=LogoImage&id='+companyLogoId;
    }

    getTabList() {
        let tabList = this.getPreferences().get('useCustomTabList') ?
            this.getPreferences().get('tabList') :
            this.getConfig().get('tabList');

        tabList = Espo.Utils.cloneDeep(tabList || []);

        if (this.isSide()) {
            tabList.unshift('Home');
        }

        return tabList;
    }

    getQuickCreateList() {
        return this.getConfig().get('quickCreateList') || [];
    }

    setup() {
        this.getRouter().on('routed', (e) => {
            if (e.controller) {
                this.selectTab(e.controller);

                return;
            }

            this.selectTab(false);
        });

        this.createView('notificationsBadge', 'views/notification/badge', {
            selector: '.notifications-badge-container',
        });

        const setup = () => {
            this.setupQuickCreateList();
            this.setupTabDefsList();
        };

        this.setupGlobalSearch();

        setup();

        this.listenTo(this.getHelper().settings, 'sync', () => {
            setup();

            this.reRender();
        });

        this.listenTo(this.getHelper().language, 'sync', () => {
            setup();

            this.reRender();
        });

        this.once('remove', () => {
            $(window).off('resize.navbar');
            $(window).off('scroll.navbar');
            $(window).off('scroll.navbar-tab-group');

            this.$body.removeClass('has-navbar');
        });
    }

    setupQuickCreateList() {
        const scopes = this.getMetadata().get('scopes') || {};

        this.quickCreateList = this.getQuickCreateList().filter(scope =>{
            if (!scopes[scope]) {
                return false;
            }

            if ((scopes[scope] || {}).disabled) {
                return;
            }

            if ((scopes[scope] || {}).acl) {
                return this.getAcl().check(scope, 'create');
            }

            return true;
        });
    }

    filterTabItem(scope) {
        if (~['Home', '_delimiter_', '_delimiter-ext_'].indexOf(scope)) {
            return true;
        }

        const scopes = this.getMetadata().get('scopes') || {};

        if (!scopes[scope]) {
            return false;
        }

        const defs = /** @type {{disabled?: boolean, acl?: boolean, tabAclPermission?: string}} */
            scopes[scope] || {};

        if (defs.disabled) {
            return;
        }

        if (defs.acl) {
            return this.getAcl().check(scope);
        }

        if (defs.tabAclPermission) {
            const level = this.getAcl().getPermissionLevel(defs.tabAclPermission);

            return level && level !== 'no';
        }

        return true;
    }

    setupGlobalSearch() {
        this.globalSearchAvailable = false;

        (this.getConfig().get('globalSearchEntityList') || []).forEach(scope => {
            if (this.globalSearchAvailable) {
                return;
            }

            if (this.getAcl().checkScope(scope)) {
                this.globalSearchAvailable = true;
            }
        });

        if (this.globalSearchAvailable) {
            this.createView('globalSearch', 'views/global-search/global-search', {
                selector: '.global-search-container',
            });
        }
    }

    adjustTop() {
        const smallScreenWidth = this.getThemeManager().getParam('screenWidthXs');
        const navbarHeight = this.getNavbarHeight();

        const $window = $(window);

        const $tabs = this.$tabs;
        const $more = this.$more;
        const $moreDropdown = this.$moreDropdown;

        $window.on('resize.navbar', () => updateWidth());

        $window.on('scroll.navbar', () => {
            if (!this.isMoreDropdownShown) {
                return;
            }

            $more.scrollTop($window.scrollTop());
        });

        this.$moreDropdown.on('shown.bs.dropdown', () => {
            $more.scrollTop($window.scrollTop());
        });

        this.on('show-more-tabs', () => {
            $more.scrollTop($window.scrollTop());
        });

        const updateMoreHeight = () => {
            const windowHeight = window.innerHeight;
            const windowWidth = window.innerWidth;

            if (windowWidth < smallScreenWidth) {
                $more.css('max-height', '');
                $more.css('overflow-y', '');
            } else {
                $more.css('overflow-y', 'hidden');
                $more.css('max-height', (windowHeight - navbarHeight) + 'px');
            }
        };

        $window.on('resize.navbar', () => {
            updateMoreHeight();
        });

        updateMoreHeight();

        const hideOneTab = () => {
            const count = $tabs.children().length;

            if (count <= 1) {
                return;
            }

            const $one = $tabs.children().eq(count - 2);

            $one.prependTo($more);
        };

        const unhideOneTab = () => {
            const $one = $more.children().eq(0);

            if ($one.length) {
                $one.insertBefore($moreDropdown);
            }
        };

        const $navbar = $('#navbar .navbar');

        if (window.innerWidth >= smallScreenWidth) {
            $tabs.children('li').each(() => {
                hideOneTab();
            });

            $navbar.css('max-height', 'unset');
            $navbar.css('overflow', 'visible');
        }

        const navbarBaseWidth = this.getThemeManager().getParam('navbarBaseWidth') || 555;

        const tabCount = this.tabList.length;

        const navbarNeededHeight = navbarHeight + 1;

        this.adjustBodyMinHeightMethodName = 'adjustBodyMinHeightTop';

        const $moreDd = $('#nav-more-tabs-dropdown');
        const $moreLi = $moreDd.closest('li');

        const updateWidth = () => {
            const windowWidth = window.innerWidth;
            const moreWidth = $moreLi.width();

            $more.children('li.not-in-more').each(() => {
                unhideOneTab();
            });

            if (windowWidth < smallScreenWidth) {
                return;
            }

            $navbar.css('max-height', navbarHeight + 'px');
            $navbar.css('overflow', 'hidden');

            $more.parent().addClass('hidden');

            const headerWidth = this.$el.width();

            const maxWidth = headerWidth - navbarBaseWidth - moreWidth;
            let width = $tabs.width();

            let i = 0;

            while (width > maxWidth) {
                hideOneTab();
                width = $tabs.width();
                i++;

                if (i >= tabCount) {
                    setTimeout(() => updateWidth(), 100);

                    break;
                }
            }

            $navbar.css('max-height', 'unset');
            $navbar.css('overflow', 'visible');

            if ($more.children().length > 0) {
                $moreDropdown.removeClass('hidden');
            }
        };

        const processUpdateWidth = isRecursive => {
            if ($navbar.height() > navbarNeededHeight) {
                updateWidth();
                setTimeout(() => processUpdateWidth(true), 200);

                return;
            }

            if (!isRecursive) {
                updateWidth();
                setTimeout(() => processUpdateWidth(true), 10);
            }

            setTimeout(() => processUpdateWidth(true), 1000);
        };

        if ($navbar.height() <= navbarNeededHeight && $more.children().length === 0) {
            $more.parent().addClass('hidden');
        }

        processUpdateWidth();
    }

    adjustSide() {
        const smallScreenWidth = this.getThemeManager().getParam('screenWidthXs');
        const navbarStaticItemsHeight = this.getStaticItemsHeight();

        const $window = $(window);
        const $tabs = this.$tabs;
        const $more = this.$more;

        this.adjustBodyMinHeightMethodName = 'adjustBodyMinHeightSide';

        if ($more.children().length === 0) {
            $more.parent().addClass('hidden');
        }

        $window.on('scroll.navbar', () => {
            $window.scrollTop() ?
                this.$navbarRight.addClass('shadowed') :
                this.$navbarRight.removeClass('shadowed');

            $tabs.scrollTop($window.scrollTop());

            if (!this.isMoreDropdownShown) {
                return;
            }

            $more.scrollTop($window.scrollTop());
        });

        this.$moreDropdown.on('shown.bs.dropdown', () => {
            $more.scrollTop($window.scrollTop());
        });

        this.on('show-more-tabs', () => {
            $more.scrollTop($window.scrollTop());
        });

        const updateSizeForSide = () => {
            const windowHeight = window.innerHeight;
            const windowWidth = window.innerWidth;

            this.$minimizer.removeClass('hidden');

            if (windowWidth < smallScreenWidth) {
                $tabs.css('height', 'auto');
                $more.css('max-height', '');

                return;
            }

            $tabs.css('height', (windowHeight - navbarStaticItemsHeight) + 'px');
            $more.css('max-height', windowHeight + 'px');
        };

        $(window).on('resize.navbar', () => {
            updateSizeForSide();
        });

        updateSizeForSide();

        this.adjustBodyMinHeight();
    }

    getNavbarHeight() {
        return this.getThemeManager().getParam('navbarHeight') || 43;
    }

    isSide() {
        return this.getThemeManager().getParam('navbar') === 'side';
    }

    getStaticItemsHeight() {
        return this.getThemeManager().getParam('navbarStaticItemsHeight') || 97;
    }

    adjustBodyMinHeight() {
        if (!this.adjustBodyMinHeightMethodName) {
            return;
        }

        this[this.adjustBodyMinHeightMethodName]();
    }

    adjustBodyMinHeightSide() {
        let minHeight = this.$tabs.get(0).scrollHeight + this.getStaticItemsHeight();

        let moreHeight = 0;

        this.$more.find('> li:visible').each((i, el) => {
            const $el = $(el);

            moreHeight += $el.outerHeight(true);
        });

        minHeight = Math.max(minHeight, moreHeight);

        const tabHeight = this.$tabs.find('> .tab:not(.tab-divider)').height();

        this.tabList.forEach((item, i) => {
            if (typeof item !== 'object') {
                return;
            }

            const $li = this.$el.find('li.tab[data-name="group-' + i + '"]');

            if (!$li.hasClass('open')) {
                return;
            }

            const tabCount = (item.itemList || []).length;

            const menuHeight = tabHeight * tabCount;

            if (menuHeight > minHeight) {
                minHeight = menuHeight;
            }
        });

        this.$body.css('minHeight', minHeight + 'px');
    }

    adjustBodyMinHeightTop() {
        let minHeight = this.getNavbarHeight();

        this.$more.find('> li').each((i, el) => {
            const $el = $(el);

            if (!this.isMoreTabsShown) {
                if ($el.hasClass('after-show-more')) {
                    return;
                }
            }
            else {
                if ($el.hasClass('show-more')) {
                    return;
                }
            }

            minHeight += $el.height();
        });

        const tabHeight = this.$tabs.find('.tab-group > ul > li:visible').height();

        this.tabList.forEach((item, i) => {
            if (typeof item !== 'object') {
                return;
            }

            const $li = this.$el.find('li.tab[data-name="group-' + i + '"]');

            if (!$li.hasClass('open')) {
                return;
            }

            const tabCount = (item.itemList || []).length;

            const menuHeight = tabHeight * tabCount;

            if (menuHeight > minHeight) {
                minHeight = menuHeight;
            }
        });

        this.$body.css('minHeight', minHeight + 'px');
    }

    afterRender() {
        this.$body = $('body');
        this.$tabs = this.$el.find('ul.tabs');
        this.$more = this.$tabs.find('li.more > ul');
        this.$minimizer = this.$el.find('a.minimizer');

        this.$body.addClass('has-navbar');

        const $moreDd = this.$moreDropdown = this.$tabs.find('li.more');

        $moreDd.on('shown.bs.dropdown', () => {
            this.isMoreDropdownShown = true;
            this.adjustBodyMinHeight();
        });

        $moreDd.on('hidden.bs.dropdown', () => {
            this.isMoreDropdownShown = false;
            this.hideMoreTabs();
            this.adjustBodyMinHeight();
        });

        this.selectTab(this.getRouter().getLast().controller);

        let layoutState = this.getStorage().get('state', 'siteLayoutState');

        if (!layoutState) {
            layoutState = $(window).width() > 1320 ? 'expanded' : 'collapsed';
        }

        let layoutMinimized = false;

        if (layoutState === 'collapsed') {
            layoutMinimized = true;
        }

        if (layoutMinimized) {
            this.$body.addClass('minimized');
        }

        this.$navbar = this.$el.find('> .navbar');
        this.$navbarRightContainer = this.$navbar.find('> .navbar-body > .navbar-right-container');
        this.$navbarRight = this.$navbarRightContainer.children();

        const handlerClassName = this.getThemeManager().getParam('navbarAdjustmentHandler');

        if (handlerClassName) {
            Espo.loader.require(handlerClassName, Handler => {
                const handler = new Handler(this);

                this.navbarAdjustmentHandler = handler;

                handler.process();
            });

            return;
        }

        if (this.getThemeManager().getParam('skipDefaultNavbarAdjustment')) {
            return;
        }

        this.adjustAfterRender();
    }

    adjustAfterRender() {
        if (this.isSide()) {
            const processSide = () => {
                if (this.$navbar.height() < $(window).height() / 2) {
                    setTimeout(() => processSide(), 50);

                    return;
                }

                if (this.getThemeManager().isUserTheme()) {
                    setTimeout(() => this.adjustSide(), 10);

                    return;
                }

                this.adjustSide();
            };

            processSide();

            return;
        }

        const process = () => {
            if (this.$el.width() < $(window).width() / 2) {
                setTimeout(() => process(), 50);

                return;
            }

            if (this.getThemeManager().isUserTheme()) {
                setTimeout(() => this.adjustTop(), 10);

                return;
            }

            this.adjustTop();
        };

        process();
    }

    selectTab(name) {
        if (this.currentTab !== name) {
            this.$el.find('ul.tabs li.active').removeClass('active');

            if (name) {
                this.$el.find('ul.tabs li[data-name="' + name + '"]').addClass('active');
            }

            this.currentTab = name;
        }
    }

    setupTabDefsList() {
        const tabList = this.getTabList();

        this.tabList = tabList.filter(item => {
            if (!item) {
                return false;
            }

            if (typeof item === 'object') {
                if (item.type === 'divider') {
                    if (!this.isSide()) {
                        return false;
                    }

                    return true;
                }

                item.itemList = item.itemList || [];

                item.itemList = item.itemList.filter(item => {
                    return this.filterTabItem(item);
                });

                return !!item.itemList.length;
            }

            return this.filterTabItem(item);
        });

        function isMoreDelimiter(item) {
            return item === '_delimiter_' || item === '_delimiter-ext_';
        }

        function isDivider(item) {
            return typeof item === 'object' && item.type === 'divider';
        }

        let moreIsMet = false;

        this.tabList = this.tabList.filter((item, i) => {
            const nextItem = this.tabList[i + 1];
            const prevItem = this.tabList[i - 1];

            if (isMoreDelimiter(item)) {
                moreIsMet = true;
            }

            if (!isDivider(item)) {
                return true;
            }

            if (!nextItem) {
                return true;
            }

            if (isDivider(nextItem)) {
                return false;
            }

            if (isDivider(prevItem) && isMoreDelimiter(nextItem) && moreIsMet) {
                return false;
            }

            return true;
        });

        const tabDefsList = [];

        const colorsDisabled =
            this.getPreferences().get('scopeColorsDisabled') ||
            this.getPreferences().get('tabColorsDisabled') ||
            this.getConfig().get('scopeColorsDisabled') ||
            this.getConfig().get('tabColorsDisabled');

        const tabIconsDisabled = this.getConfig().get('tabIconsDisabled');

        const params = {
            colorsDisabled: colorsDisabled,
            tabIconsDisabled: tabIconsDisabled,
        };

        const vars = {
            moreIsMet: false,
            isHidden: false,
        };

        this.tabList.forEach((tab, i) => {
            if (isMoreDelimiter(tab)) {
                if (!vars.moreIsMet) {
                    vars.moreIsMet = true;

                    return;
                }

                if (i === this.tabList.length - 1) {
                    return;
                }

                vars.isHidden = true;

                tabDefsList.push({
                    name: 'show-more',
                    isInMore: true,
                    className: 'show-more',
                    html: '<span class="fas fa-ellipsis-h more-icon"></span>',
                });

                return;
            }

            tabDefsList.push(
                this.prepareTabItemDefs(params, tab, i, vars)
            );
        });

        this.tabDefsList = tabDefsList;
    }

    prepareTabItemDefs(params, tab, i, vars) {
        let label;
        let link;

        let iconClass = null;
        let color = null;
        let isGroup = false;
        let isDivider = false;
        let name = tab;
        let aClassName = 'nav-link';

        const translateLabel = label => {
            if (label.indexOf('$') === 0) {
                return this.translate(label.slice(1), 'navbarTabs');
            }

            return label;
        };

        if (tab === 'Home') {
            label = this.getLanguage().translate(tab);
            link = '#';
        }
        else if (typeof tab === 'object' && tab.type === 'divider') {
            isDivider = true;
            label = tab.text;
            aClassName = 'nav-divider-text';
            name = 'divider-' + i;

            if (label) {
                label = translateLabel(label);
            }
        }
        else if (typeof tab === 'object') {
            isGroup = true;

            label = tab.text || '';
            color = tab.color;
            iconClass = tab.iconClass;

            name = 'group-' + i;

            link = null;

            aClassName = 'nav-link-group';

            if (label) {
                label = translateLabel(label);
            }
        }
        else {
            label = this.getLanguage().translate(tab, 'scopeNamesPlural');
            link = '#' + tab;
        }

        label = label || '';

        const shortLabel = label.substring(0, 2);

        if (!params.colorsDisabled && !isGroup && !isDivider) {
            color = this.getMetadata().get(['clientDefs', tab, 'color']);
        }

        if (!params.tabIconsDisabled && !isGroup && !isDivider) {
            iconClass = this.getMetadata().get(['clientDefs', tab, 'iconClass'])
        }

        const o = {
            link: link,
            label: label,
            shortLabel: shortLabel,
            name: name,
            isInMore: vars.moreIsMet,
            color: color,
            iconClass: iconClass,
            isAfterShowMore: vars.isHidden,
            aClassName: aClassName,
            isGroup: isGroup,
            isDivider: isDivider,
        };

        if (isGroup) {
            o.itemList = tab.itemList.map((tab, i) => {
                return this.prepareTabItemDefs(params, tab, i, vars);
            });
        }

        if (vars.isHidden) {
            o.className = 'after-show-more';
        }

        if (color && !iconClass) {
            o.colorIconClass = 'color-icon fas fa-square';
        }

        return o;
    }

    /**
     * @typedef {Object} MenuDataItem
     * @property {string} [link]
     * @property {string} [html]
     * @property {true} [divider]
     */

    /**
     * @return {MenuDataItem[]}
     */
    getMenuDataList() {
        let avatarHtml = this.getHelper().getAvatarHtml(this.getUser().id, 'small', 16, 'avatar-link');

        if (avatarHtml) {
            avatarHtml += ' ';
        }

        /** @type {MenuDataItem[]}*/
        let list = [
            {
                link: '#User/view/' + this.getUser().id,
                html: avatarHtml + this.getHelper().escapeString(this.getUser().get('name')),
            },
            {divider: true}
        ];

        if (this.getUser().isAdmin()) {
            list.push({
                link: '#Admin',
                label: this.getLanguage().translate('Administration'),
            });
        }

        list.push({
            link: '#Preferences',
            label: this.getLanguage().translate('Preferences'),
        });

        if (!this.getConfig().get('actionHistoryDisabled')) {
            list.push({divider: true});

            list.push({
                action: 'showLastViewed',
                link: '#LastViewed',
                label: this.getLanguage().translate('LastViewed', 'scopeNamesPlural'),
            });
        }

        list = list.concat([
            {
                divider: true
            },
            {
                link: '#About',
                label: this.getLanguage().translate('About')
            },
            {
                action: 'logout',
                label: this.getLanguage().translate('Log Out')
            },
        ]);

        return list;
    }

    quickCreate(scope) {
        Espo.Ui.notify(' ... ');

        const type = this.getMetadata().get(['clientDefs', scope, 'quickCreateModalType']) || 'edit';
        const viewName = this.getMetadata().get(['clientDefs', scope, 'modalViews', type]) || 'views/modals/edit';

        this.createView('quickCreate', viewName , {scope: scope}, (view) => {
            view.once('after:render', () => Espo.Ui.notify(false));

            view.render();
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionLogout() {
        this.getRouter().logout();
    }

    // noinspection JSUnusedGlobalSymbols
    actionShowLastViewed() {
        Espo.Ui.notify(' ... ');

        this.createView('dialog', 'views/modals/last-viewed', {}, (view) => {
            view.render();

            Espo.Ui.notify(false);

            this.listenToOnce(view, 'close', () => {
                this.clearView('dialog');
            });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionShowHistory() {
        this.createView('dialog', 'views/modals/action-history', {}, (view) => {
            view.render();

            this.listenTo(view, 'close', () => {
                this.clearView('dialog');
            });
        });
    }

    showMoreTabs() {
        this.$el.find('.tab-group.tab.dropdown').removeClass('open');

        this.isMoreTabsShown = true;
        this.$more.addClass('more-expanded');
        this.adjustBodyMinHeight();
        this.trigger('show-more-tabs');
    }

    hideMoreTabs() {
        if (!this.isMoreTabsShown) {
            return;
        }

        this.$more.removeClass('more-expanded');
        this.adjustBodyMinHeight();
        this.isMoreTabsShown = false;
    }
}

export default NavbarSiteView;
PK]떪s��views/site/footer.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';

class FooterSiteView extends View {

    template = 'site/footer'
}

export default FooterSiteView;
PK]o�8i��views/team/record/edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/team/record/edit', ['views/record/edit'], function (Dep) {

    return Dep.extend({

    });
});
PK]�f���views/team/record/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/team/record/detail', ['views/record/detail'], function (Dep) {

    return Dep.extend({

    });
});
PK]��?�uuviews/team/record/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/team/record/list', ['views/record/list'], function (Dep) {

    return Dep.extend({

    	quickDetailDisabled: true,

        quickEditDisabled: true,

        massActionList: ['remove'],

        checkAllResultDisabled: true,

    });
});
PK]/��qviews/team/modals/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/team/modals/detail', ['views/modals/detail'], function (Dep) {

    return Dep.extend({

        editDisabled: true,

    });
});
PK]��J#	#	$views/preferences/fields/language.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/preferences/fields/language', ['views/fields/enum'], function (Dep) {

    return Dep.extend({

        setupOptions: function () {
            this.params.options =
                Espo.Utils.clone(this.getMetadata().get(['app', 'language', 'list']) || [])
                    .sort((v1, v2) => {
                        return this.getLanguage().translateOption(v1, 'language')
                            .localeCompare(this.getLanguage().translateOption(v2, 'language'));
                    });

            this.params.options.unshift('');

            this.translatedOptions = Espo.Utils.clone(this.getLanguage().translate('language', 'options') || {});

            var defaultTranslated =  this.translatedOptions[this.getConfig().get('language')] || this.getConfig().get('language');

            this.translatedOptions[''] = this.translate('Default') + ' (' + defaultTranslated + ')';
        },
    });
});
PK]�\7�mm&views/preferences/fields/week-start.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/preferences/fields/week-start', ['views/fields/enum-int'], function (Dep) {

    return Dep.extend({

        setupOptions: function () {
            this.params.options = Espo.Utils.clone(this.params.options);

            this.params.options.unshift(-1);

            this.translatedOptions = {};

            var dayList = this.getLanguage().get('Global', 'lists', 'dayNames') || [];

            dayList.forEach((item, i) => {
                this.translatedOptions[i] = item;
            });

            var defaultWeekStart = this.getConfig().get('weekStart');

            this.translatedOptions[-1] = this.translate('Default') +
                ' (' + dayList[defaultWeekStart] +
                ')';
        },
    });
});
PK]r�.���Lviews/preferences/fields/assignment-notifications-ignore-entity-type-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/preferences/fields/assignment-notifications-ignore-entity-type-list',
['views/fields/checklist'], function (Dep) {

    return Dep.extend({

        isInversed: true,

        setupOptions: function () {
            this.params.options = Espo.Utils.clone(this.getConfig().get('assignmentNotificationsEntityList')) || [];
        },
    });
});
PK]�"�2��'views/preferences/fields/time-format.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/preferences/fields/time-format', ['views/fields/enum'], function (Dep) {

    return Dep.extend({

        setupOptions: function () {
            this.params.options = Espo.Utils.clone(
                this.getMetadata().get(['app', 'dateTime', 'timeFormatList']) || []
            );

            this.params.options.unshift('');

            this.translatedOptions = this.translatedOptions || {};

            this.translatedOptions[''] = this.translate('Default') +
                ' (' + this.getConfig().get('timeFormat') +')';
        },
    });
});
PK]M��oo.views/preferences/fields/dashboard-tab-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/preferences/fields/dashboard-tab-list', ['views/fields/array'], function (Dep) {

    return Dep.extend({

        maxItemLength: 36,

        setup: function () {
            Dep.prototype.setup.call(this);

            this.translatedOptions = {};

            let list = this.model.get(this.name) || [];

            list.forEach(value => {
                this.translatedOptions[value] = value;
            });

            this.validations.push('uniqueLabel');
        },

        getItemHtml: function (value) {
            value = value.toString();

            let translatedValue = this.translatedOptions[value] || value;

            return $('<div>')
                .addClass('list-group-item link-with-role form-inline')
                .attr('data-value', value)
                .append(
                    $('<div>')
                        .addClass('pull-left')
                        .css('width', '92%')
                        .css('display', 'inline-block')
                        .append(
                            $('<input>')
                                .attr('maxLength', this.maxItemLength)
                                .attr('data-name', 'translatedValue')
                                .attr('data-value', value)
                                .addClass('role form-control input-sm')
                                .attr('value', translatedValue)
                                .css('width', '65%')
                        )
                )
                .append(
                    $('<div>')
                        .css('width', '8%')
                        .css('display', 'inline-block')
                        .css('vertical-align', 'top')
                        .append(
                            $('<a>')
                                .attr('role', 'button')
                                .attr('tabindex', '0')
                                .addClass('pull-right')
                                .attr('data-value', value)
                                .attr('data-action', 'removeValue')
                                .append(
                                    $('<span>').addClass('fas fa-times')
                                )
                        )
                )
                .append(
                    $('<br>').css('clear', 'both')
                )
                .get(0).outerHTML;
        },

        validateUniqueLabel: function () {
            let keyList = this.model.get(this.name) || [];
            let labels = this.model.get('translatedOptions') || {};
            let metLabelList = [];

            for (let key of keyList) {
                let label = labels[key];

                if (!label) {
                    return true;
                }

                if (metLabelList.indexOf(label) !== -1) {
                    return true;
                }

                metLabelList.push(label);
            }

            return false;
        },

        fetch: function () {
            let data = Dep.prototype.fetch.call(this);

            data.translatedOptions = {};

            (data[this.name] || []).forEach(value => {
                let valueInternal = value.replace(/"/g, '\\"');

                data.translatedOptions[value] = this.$el
                    .find('input[data-name="translatedValue"][data-value="'+valueInternal+'"]')
                    .val() || value;
            });

            return data;
        },
    });
});
PK]H:���Rviews/preferences/fields/assignment-email-notifications-ignore-entity-type-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/preferences/fields/assignment-email-notifications-ignore-entity-type-list',
['views/fields/checklist'], function (Dep) {

    return Dep.extend({

        isInversed: true,

        setupOptions: function () {
            this.params.options = Espo.Utils.clone(
                this.getConfig().get('assignmentEmailNotificationsEntityList')) || [];
        },
    });
});
PK]��A~f	f	$views/preferences/fields/tab-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/preferences/fields/tab-list', ['views/settings/fields/tab-list'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            this.params.options = this.params.options.filter(scope => {
                if (scope === '_delimiter_' || scope === 'Home') {
                    return true;
                }

                let defs = this.getMetadata().get(['scopes', scope]);

                if (!defs) {
                    return;
                }

                if (defs.disabled) {
                    return;
                }

                if (defs.acl) {
                    return this.getAcl().check(scope);
                }

                if (defs.tabAclPermission) {
                    let level = this.getAcl().get(defs.tabAclPermission);

                    return level && level !== 'no';
                }

                return true;
            });
        },
    });
});
PK]M�aw��8views/preferences/fields/auto-follow-entity-type-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/preferences/fields/auto-follow-entity-type-list', ['views/fields/multi-enum'], function (Dep) {

    return Dep.extend({

        setup: function () {
            this.params.options = Object.keys(this.getMetadata().get('scopes'))
                .filter(scope => {
                    if (this.getMetadata().get('scopes.' + scope + '.disabled')) {
                        return;
                    }

                    return this.getMetadata().get('scopes.' + scope + '.entity') &&
                        this.getMetadata().get('scopes.' + scope + '.stream');
                })
                .sort((v1, v2) => {
                    return this.translate(v1, 'scopeNamesPlural')
                        .localeCompare(this.translate(v2, 'scopeNamesPlural'));
                });

            Dep.prototype.setup.call(this);
        },
    });
});
PK])�@ն�'views/preferences/fields/date-format.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/preferences/fields/date-format', ['views/fields/enum'], function (Dep) {

    return Dep.extend({

        setupOptions: function () {
            this.params.options = Espo.Utils.clone(
                this.getMetadata().get(['app', 'dateTime', 'dateFormatList']) || []
            );

            this.params.options.unshift('');

            this.translatedOptions = this.translatedOptions || {};

            this.translatedOptions[''] = this.translate('Default') +
                ' (' + this.getConfig().get('dateFormat') +')';
        },
    });
});
PK]������,views/preferences/fields/default-currency.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/preferences/fields/default-currency', ['views/fields/enum'], function (Dep) {

    return Dep.extend({

        setupOptions: function () {
            this.params.options = Espo.Utils.clone(this.getConfig().get('currencyList') || []);
            this.params.options.unshift('');

            this.translatedOptions = this.translatedOptions || {};
            this.translatedOptions[''] = this.translate('Default') +
                ' (' + this.getConfig().get('defaultCurrency') +')';
        },
    });
});
PK]dm�@!views/preferences/fields/theme.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/preferences/fields/theme', ['views/settings/fields/theme'], function (Dep) {

    return Dep.extend({

        setupOptions: function () {
            this.params.options = Object.keys(this.getMetadata().get('themes') || {})
                .sort((v1, v2) => {
                    if (v2 === 'EspoRtl') {
                        return -1;
                    }

                    return this.translate(v1, 'themes').localeCompare(this.translate(v2, 'themes'));
                });

            this.params.options.unshift('');
        },

        setupTranslation: function () {
            Dep.prototype.setupTranslation.call(this);

            this.translatedOptions = this.translatedOptions || {};

            let defaultTheme = this.getConfig().get('theme');
            let defaultTranslated = this.translatedOptions[defaultTheme] || defaultTheme;

            this.translatedOptions[''] = this.translate('Default') + ' (' + defaultTranslated + ')';
        },

        afterRenderDetail: function () {
            let navbar = this.getNavbarValue() || this.getDefaultNavbar();

            if (navbar) {
                this.$el
                    .append(' ')
                    .append(
                        $('<span>').addClass('text-muted chevron-right')
                    )
                    .append(' ')
                    .append(
                        $('<span>').text(this.translate(navbar, 'themeNavbars'))
                    )
            }
        },
    });
});
PK] ��mm%views/preferences/fields/time-zone.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/preferences/fields/time-zone', ['views/fields/enum'], function (Dep) {

    return Dep.extend({

        setupOptions: function () {
            this.params.options = Espo.Utils.clone(this.getHelper().getAppParam('timeZoneList')) || [];
            this.params.options.unshift('');

            this.translatedOptions = this.translatedOptions || {};
            this.translatedOptions[''] = this.translate('Default') + ' (' + this.getConfig().get('timeZone') + ')';
        },
    });
});
PK]��e~%views/preferences/fields/signature.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/preferences/fields/signature', ['views/fields/wysiwyg'], function (Dep) {

    return Dep.extend({

        fetchEmptyValueAsNull: true,

        toolbar: [
            ["style", ["bold", "italic", "underline", "clear"]],
            ["color", ["color"]],
            ["height", ["height"]],
            ['table', ['espoLink']],
            ["misc",["codeview", "fullscreen"]],
        ],
    });
});
PK]/n$�^^views/preferences/edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/preferences/edit', ['views/edit'], function (Dep) {

    return Dep.extend({

        userName: '',

        setup: function () {
            Dep.prototype.setup.call(this);

            this.userName = this.model.get('name');
        },

        getHeader: function () {
            return this.buildHeaderHtml([
                $('<span>').text(this.translate('Preferences')),
                $('<span>').text(this.userName),
            ]);
        },
    });
});
PK]��BC5'5' views/preferences/record/edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/preferences/record/edit', ['views/record/edit'], function (Dep) {

    return Dep.extend({

        sideView: null,

        saveAndContinueEditingAction: false,

        buttonList: [
            {
                name: 'save',
                label: 'Save',
                style: 'primary',
            },
            {
                name: 'cancel',
                label: 'Cancel',
            }
        ],

        dynamicLogicDefs: {
            fields: {
                'tabList': {
                    visible: {
                        conditionGroup: [
                            {
                                type: 'isTrue',
                                attribute: 'useCustomTabList',
                            }
                        ]
                    }
                },
            },
        },

        setup: function () {
            Dep.prototype.setup.call(this);

            this.addDropdownItem({
                name: 'reset',
                text: this.getLanguage().translate('Reset to Default', 'labels', 'Admin'),
                style: 'danger'
            });

            var forbiddenEditFieldList = this.getAcl().getScopeForbiddenFieldList('Preferences', 'edit');

            if (!~forbiddenEditFieldList.indexOf('dashboardLayout') && !this.model.isPortal()) {
                this.addDropdownItem({
                    name: 'resetDashboard',
                    text: this.getLanguage().translate('Reset Dashboard to Default', 'labels', 'Preferences')
                });
            }

            if (this.model.isPortal()) {
                this.layoutName = 'detailPortal';
            }

            if (this.model.id === this.getUser().id) {
                this.on('after:save', () => {
                    let data = this.model.getClonedAttributes();

                    delete data['smtpPassword'];

                    this.getPreferences().set(data);
                    this.getPreferences().trigger('update');
                });
            }

            if (!this.getUser().isAdmin() || this.model.isPortal()) {
                this.hideField('dashboardLayout');
            }

            this.controlFollowCreatedEntityListVisibility();
            this.listenTo(this.model, 'change:followCreatedEntities', this.controlFollowCreatedEntityListVisibility);

            this.controlColorsField();
            this.listenTo(this.model, 'change:scopeColorsDisabled', this.controlColorsField, this);

            var hideNotificationPanel = true;

            if (!this.getConfig().get('assignmentEmailNotifications') || this.model.isPortal()) {
                this.hideField('receiveAssignmentEmailNotifications');
                this.hideField('assignmentEmailNotificationsIgnoreEntityTypeList');
            } else {
                hideNotificationPanel = false;

                this.controlAssignmentEmailNotificationsVisibility();

                this.listenTo(this.model, 'change:receiveAssignmentEmailNotifications', () => {
                    this.controlAssignmentEmailNotificationsVisibility();
                });
            }

            if ((this.getConfig().get('assignmentEmailNotificationsEntityList') || []).length === 0) {
                this.hideField('assignmentEmailNotificationsIgnoreEntityTypeList');
            }

            if (
                (this.getConfig().get('assignmentNotificationsEntityList') || []).length === 0 ||
                this.model.isPortal()
            ) {
                this.hideField('assignmentNotificationsIgnoreEntityTypeList');
            } else {
                hideNotificationPanel = false;
            }

            if (this.getConfig().get('emailForceUseExternalClient')) {
                this.hideField('emailUseExternalClient');
            }

            if (!this.getConfig().get('mentionEmailNotifications') || this.model.isPortal()) {
                this.hideField('receiveMentionEmailNotifications');
            } else {
                hideNotificationPanel = false;
            }

            if (!this.getConfig().get('streamEmailNotifications') && !this.model.isPortal()) {
                this.hideField('receiveStreamEmailNotifications');
            } else if (!this.getConfig().get('portalStreamEmailNotifications') && this.model.isPortal()) {
                this.hideField('receiveStreamEmailNotifications');
            } else {
                hideNotificationPanel = false;
            }

            if (this.getConfig().get('scopeColorsDisabled')) {
                this.hideField('scopeColorsDisabled');
                this.hideField('tabColorsDisabled');
            }

            if (this.getConfig().get('tabColorsDisabled')) {
                this.hideField('tabColorsDisabled');
            }

            if (hideNotificationPanel) {
                this.hidePanel('notifications');
            }

            if (this.getConfig().get('userThemesDisabled')) {
                this.hideField('theme');
            }

            this.on('save', initialAttributes => {
                if (
                    this.model.get('language') !== initialAttributes.language ||
                    this.model.get('theme') !== initialAttributes.theme ||
                    (this.model.get('themeParams') || {}).navbar !== (initialAttributes.themeParams || {}).navbar
                ) {
                    this.setConfirmLeaveOut(false);

                    window.location.reload();
                }
            });
        },

        controlFollowCreatedEntityListVisibility: function () {
            if (!this.model.get('followCreatedEntities')) {
                this.showField('followCreatedEntityTypeList');
            } else {
                this.hideField('followCreatedEntityTypeList');
            }
        },

        controlColorsField: function () {
            if (this.model.get('scopeColorsDisabled')) {
                this.hideField('tabColorsDisabled');
            } else {
                this.showField('tabColorsDisabled');
            }
        },

        controlAssignmentEmailNotificationsVisibility: function () {
            if (this.model.get('receiveAssignmentEmailNotifications')) {
                this.showField('assignmentEmailNotificationsIgnoreEntityTypeList');
            } else {
                this.hideField('assignmentEmailNotificationsIgnoreEntityTypeList');
            }
        },

        actionReset: function () {
            this.confirm(this.translate('resetPreferencesConfirmation', 'messages'), () => {
                Espo.Ajax
                    .deleteRequest('Preferences/' + this.model.id)
                    .then(() => {
                        Espo.Ui.success(this.translate('resetPreferencesDone', 'messages'));

                        this.model.set(data);

                        for (let attribute in data) {
                            this.setInitialAttributeValue(attribute, data[attribute]);
                        }

                        this.getPreferences().set(this.model.getClonedAttributes());
                        this.getPreferences().trigger('update');

                        this.setIsNotChanged();
                    });
            });
        },

        actionResetDashboard: function () {
            this.confirm(this.translate('confirmation', 'messages'), () => {
                Espo.Ajax.postRequest('Preferences/action/resetDashboard', {id: this.model.id})
                    .then(data =>  {
                        Espo.Ui.success(this.translate('Done'));

                        this.model.set(data);

                        for (var attribute in data) {
                            this.setInitialAttributeValue(attribute, data[attribute]);
                        }

                        this.getPreferences().set(this.model.getClonedAttributes());
                        this.getPreferences().trigger('update');
                    });
            });
        },

        afterRender: function () {
            Dep.prototype.afterRender.call(this);
        },

        exit: function (after) {
            if (after === 'cancel') {
                var url = '#User/view/' + this.model.id;

                if (!this.getAcl().checkModel(this.getUser())) {
                    url = '#';
                }

                this.getRouter().navigate(url, {trigger: true});
            }
        },
    });
});
PK]\F���#views/stream/row-actions/default.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/stream/row-actions/default', ['views/record/row-actions/edit-and-remove'], function (Dep) {

    return Dep.extend({

        getActionList: function () {
            var list = [];

            if (this.options.acl.edit && this.options.isEditable) {
                list.push({
                    action: 'quickEdit',
                    label: 'Edit',
                    data: {
                        id: this.model.id,
                    },
                });
            }

            if (this.options.acl.edit && this.options.isRemovable) {
                list.push({
                    action: 'quickRemove',
                    label: 'Remove',
                    data: {
                        id: this.model.id,
                    },
                });
            }

            return list;
        },
    });
});
PK]��g5��views/stream/fields/post.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/stream/fields/post', ['views/fields/text'], function (Dep) {

    return Dep.extend({

        getValueForDisplay: function () {
            let text = Dep.prototype.getValueForDisplay.call(this);

            if (this.isDetailMode() || this.isListMode()) {
                let mentionData = (this.model.get('data') || {}).mentions || {};

                Object
                    .keys(mentionData)
                    .sort((a, b) => {
                        return a.length < b.length;
                    })
                    .forEach(item => {
                        var part = '[' + mentionData[item].name + '](#User/view/'+mentionData[item].id + ')';

                        text = text.replace(new RegExp(item, 'g'), part);
                    });
            }

            return text;
        },
    });
});
PK]`F;wDD*views/stream/fields/attachment-multiple.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/stream/fields/attachment-multiple', ['views/fields/attachment-multiple'], function (Dep) {

    return Dep.extend({

        showPreviews: true,

        showPreviewsInListMode: true,
    });
});
PK]b1»�views/stream/note.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';

class NoteStreamView extends View {

    /**
     * @protected
     * @type {string|null}
     */
    messageName = null

    /**
     * @protected
     * @type {string|null}
     */
    messageTemplate = null

    /**
     * Data to pass to a message template.
     *
     * @protected
     * @type {Object.<string,JQuery|Element|string>|null}
     */
    messageData = null

    /**
     * @protected
     */
    isEditable = false

    /**
     * @protected
     */
    isRemovable = false

    /**
     * @protected
     */
    isSystemAvatar = false

    data() {
        return {
            isUserStream: this.isUserStream,
            noEdit: this.options.noEdit,
            acl: this.options.acl,
            onlyContent: this.options.onlyContent,
            avatar: this.getAvatarHtml(),
        };
    }

    init() {
        this.createField('createdAt', null, null, 'views/fields/datetime-short');

        this.isUserStream = this.options.isUserStream;
        this.isThis = !this.isUserStream;

        this.parentModel = this.options.parentModel;

        if (!this.isUserStream) {
            if (this.parentModel) {
                if (
                    this.parentModel.entityType !== this.model.get('parentType') ||
                    this.parentModel.id !== this.model.get('parentId')
                ) {
                    this.isThis = false;
                }
            }
        }

        if (this.getUser().isAdmin()) {
            this.isRemovable = true;
        }

        if (this.messageName && this.isThis) {
            this.messageName += 'This';
        }

        if (!this.isThis) {
            this.createField('parent');
        }

        let translatedEntityType = this.translateEntityType(this.model.get('parentType'));

        this.messageData = {
            'user': 'field:createdBy',
            'entity': 'field:parent',
            'entityType': translatedEntityType,
        };

        if (!this.options.noEdit && (this.isEditable || this.isRemovable)) {
            this.createView('right', 'views/stream/row-actions/default', {
                selector: '.right-container',
                acl: this.options.acl,
                model: this.model,
                isEditable: this.isEditable,
                isRemovable: this.isRemovable,
            });
        }
    }

    translateEntityType(entityType, isPlural) {
        let string = isPlural ?
            (this.translate(entityType, 'scopeNamesPlural') || '') :
            (this.translate(entityType, 'scopeNames') || '');

        string = string.toLowerCase();

        let language = this.getPreferences().get('language') || this.getConfig().get('language');

        if (~['de_DE', 'nl_NL'].indexOf(language)) {
            string = Espo.Utils.upperCaseFirst(string);
        }

        return string;
    }

    createField(name, type, params, view, options) {
        type = type || this.model.getFieldType(name) || 'base';

        let o = {
            model: this.model,
            defs: {
                name: name,
                params: params || {}
            },
            selector: '.cell-' + name,
            mode: 'list',
        };

        if (options) {
            for (let i in options) {
                o[i] = options[i];
            }
        }

        this.createView(name, view || this.getFieldManager().getViewName(type), o);
    }

    isMale() {
        return this.model.get('createdByGender') === 'Male';
    }

    isFemale() {
        return this.model.get('createdByGender') === 'Female';
    }

    createMessage() {
        if (!this.messageTemplate) {
            let isTranslated = false;
            let parentType = this.model.get('parentType') || null;

            if (this.isMale()) {
                this.messageTemplate = this.translate(this.messageName, 'streamMessagesMale', parentType) || '';

                if (this.messageTemplate !== this.messageName) {
                    isTranslated = true;
                }
            } else if (this.isFemale()) {
                this.messageTemplate = this.translate(this.messageName, 'streamMessagesFemale', parentType) || '';

                if (this.messageTemplate !== this.messageName) {
                    isTranslated = true;
                }
            }

            if (!isTranslated) {
                this.messageTemplate = this.translate(this.messageName, 'streamMessages', parentType) || '';
            }
        }

        if (
            this.messageTemplate.indexOf('{entityType}') === 0 &&
            typeof this.messageData.entityType === 'string'
        ) {
            this.messageData.entityTypeUcFirst = Espo.Utils.upperCaseFirst(this.messageData.entityType);

            this.messageTemplate = this.messageTemplate.replace('{entityType}', '{entityTypeUcFirst}');
        }

        this.createView('message', 'views/stream/message', {
            messageTemplate: this.messageTemplate,
            selector: '.message',
            model: this.model,
            messageData: this.messageData,
        });
    }

    getAvatarHtml() {
        let id = this.model.get('createdById');

        if (this.isSystemAvatar) {
            id = this.getHelper().getAppParam('systemUserId');
        }

        return this.getHelper().getAvatarHtml(id, 'small', 20);
    }

    getIconHtml(scope, id) {
        if (this.isThis && scope === this.parentModel.entityType) {
            return;
        }

        let iconClass = this.getMetadata().get(['clientDefs', scope, 'iconClass']);

        if (!iconClass) {
            return;
        }

        return $('<span>')
            .addClass(iconClass)
            .addClass('action text-muted icon')
            .css('cursor', 'pointer')
            .attr('title', this.translate('View'))
            .attr('data-action', 'quickView')
            .attr('data-id', id)
            .attr('data-scope', scope)
            .get(0).outerHTML;
    }
}

export default NoteStreamView;
PK]?����views/stream/message.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';

class MessageStreamView extends View {

    data() {
        return this.dataForTemplate;
    }

    setup() {
        let template = this.options.messageTemplate;
        let data = Espo.Utils.clone(this.options.messageData || {});

        this.dataForTemplate = {};

        for (let key in data) {
            let value = data[key] || '';

            if (key.indexOf('html:') === 0) {
                key = key.substring(5);
                this.dataForTemplate[key] = value;
                template = template.replace('{' + key + '}', '{{{' + key + '}}}');

                continue;
            }

            if (value instanceof jQuery) {
                this.dataForTemplate[key] = value.get(0).outerHTML;
                template = template.replace('{' + key + '}', '{{{' + key + '}}}');

                continue;
            }

            if (value instanceof Element) {
                this.dataForTemplate[key] = value.outerHTML;
                template = template.replace('{' + key + '}', '{{{' + key + '}}}');

                continue;
            }

            if (!value.indexOf) {
                continue;
            }

            if (value.indexOf('field:') === 0) {
                let field = value.substring(6);
                this.createField(key, field);

                let keyEscaped = this.getHelper().escapeString(key);

                template = template.replace(
                    '{' + key + '}',
                    `<span data-key="${keyEscaped}">\{\{\{${key}\}\}\}</span>`
                );

                continue;
            }

            this.dataForTemplate[key] = value;
            template = template.replace('{' + key + '}', '{{' + key + '}}');
        }

        this.templateContent = template;
    }

    createField(key, name, type, params) {
        type = type || this.model.getFieldType(name) || 'base';

        this.createView(key, this.getFieldManager().getViewName(type), {
            model: this.model,
            defs: {
                name: name,
                params: params || {}
            },
            mode: 'detail',
            readOnly: true,
            selector: `[data-key="${key}"]`,
        });
    }
}

export default MessageStreamView;
PK]�ɳo�%�%views/stream/record/edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import BaseRecordView from 'views/record/base';

class EditStreamView extends BaseRecordView {

    template = 'stream/record/edit'

    postingMode = false

    dependencyDefs = {
        'targetType': {
            map: {
                'users': [
                    {
                        action: 'hide',
                        fields: ['teams', 'portals']
                    },
                    {
                        action: 'show',
                        fields: ['users']
                    },
                    {
                        action: 'setNotRequired',
                        fields: ['teams', 'portals']
                    },
                    {
                        action: 'setRequired',
                        fields: ['users']
                    }
                ],
                'teams': [
                    {
                        action: 'hide',
                        fields: ['users', 'portals']
                    },
                    {
                        action: 'show',
                        fields: ['teams']
                    },
                    {
                        action: 'setRequired',
                        fields: ['teams']
                    },
                    {
                        action: 'setNotRequired',
                        fields: ['users', 'portals']
                    }
                ],
                'portals': [
                    {
                        action: 'hide',
                        fields: ['users', 'teams']
                    },
                    {
                        action: 'show',
                        fields: ['portals']
                    },
                    {
                        action: 'setRequired',
                        fields: ['portals']
                    },
                    {
                        action: 'setNotRequired',
                        fields: ['users', 'teams']
                    }
                ]
            },
            default: [
                {
                    action: 'hide',
                    fields: ['teams', 'users', 'portals']
                },
                {
                    action: 'setNotRequired',
                    fields: ['teams', 'users', 'portals']
                }
            ]
        }
    }

    data() {
        let data = super.data();

        data.interactiveMode = this.options.interactiveMode;

        return data;
    }

    setup() {
        super.setup();

        this.seed = this.model.clone();

        if (this.options.interactiveMode) {
            this.events['focus textarea[data-name="post"]'] = () => {
                this.enablePostingMode();
            };

            this.events['keydown textarea[data-name="post"]'] = (e) => {
                if (Espo.Utils.getKeyFromKeyEvent(e) === 'Control+Enter') {
                    e.stopPropagation();
                    e.preventDefault();

                    this.post();
                }

                // Don't hide to be able to focus on the upload button.
                /*if (e.code === 'Tab') {
                    let $text = $(e.currentTarget);

                    if ($text.val() === '') {
                        this.disablePostingMode();
                    }
                }*/
            };

            this.events['click button.post'] = () => {
                this.post();
            };
        }

        let optionList = ['self'];

        this.model.set('type', 'Post');
        this.model.set('targetType', 'self');

        let messagePermission = this.getAcl().getPermissionLevel('message');
        let portalPermission = this.getAcl().getPermissionLevel('portal');

        if (messagePermission === 'team' || messagePermission === 'all') {
            optionList.push('users');
            optionList.push('teams');
        }

        if (messagePermission === 'all') {
            optionList.push('all');
        }

        if (portalPermission === 'yes') {
            optionList.push('portals');

            if (!~optionList.indexOf('users')) {
                optionList.push('users');
            }
        }

        this.createField('targetType', 'views/fields/enum', {
            options: optionList,
        });

        this.createField('users', 'views/note/fields/users', {});
        this.createField('teams', 'views/fields/teams', {});
        this.createField('portals', 'views/fields/link-multiple', {});
        this.createField('post', 'views/note/fields/post', {
            required: true,
            rowsMin: 1,
            rows: 25,
            noResize: true,
        });

        this.createField('attachments', 'views/stream/fields/attachment-multiple', {});

        this.listenTo(this.model, 'change', () => {
            if (this.postingMode) {
                this.setConfirmLeaveOut(true);
            }
        });
    }

    disablePostingMode() {
        this.postingMode = false;

        this.$el.find('.post-control').addClass('hidden');

        this.setConfirmLeaveOut(false);

        $('body').off('click.stream-create-post');

        this.getFieldView('post').$element.prop('rows', 1);
    }

    enablePostingMode() {
        this.$el.find('.post-control').removeClass('hidden');

        if (!this.postingMode) {
            let $body = $('body');

            $body.off('click.stream-create-post');

            $body.on('click.stream-create-post', e => {
                if (
                    $.contains(window.document.body, e.target) &&
                    !$.contains(this.$el.get(0), e.target) &&
                    !$(e.target).closest('.modal-dialog').length
                ) {
                    if (this.getFieldView('post') && this.getFieldView('post').$element.val() === '') {
                        if (!(this.model.get('attachmentsIds') || []).length) {
                            this.disablePostingMode();
                        }
                    }
                }
            });
        }

        this.postingMode = true;
    }

    afterRender() {
        this.$postButton = this.$el.find('button.post');

        let postView = this.getFieldView('post');

        if (postView) {
            this.stopListening(postView, 'add-files');

            this.listenTo(postView, 'add-files', (files) => {
                this.enablePostingMode();

                let attachmentsView = /** @type module:views/fields/attachment-multiple */
                    this.getFieldView('attachments');

                if (!attachmentsView) {
                    return;
                }

                attachmentsView.uploadFiles(files);
            });
        }
    }

    validate() {
        let notValid = super.validate();

        let message = this.model.get('post') || '';

        if (message.trim() === '' && !(this.model.get('attachmentsIds') || []).length) {
            notValid = true;
        }

        return notValid;
    }

    post() {
        this.save();
    }

    beforeBeforeSave() {
        this.disablePostButton();
    }

    beforeSave() {
        Espo.Ui.notify(' ... ');
    }

    afterSave() {
        Espo.Ui.success(this.translate('Posted'));

        if (this.options.interactiveMode) {
            this.model.clear();
            this.model.set('targetType', 'self');
            this.model.set('type', 'Post');

            this.disablePostingMode();
            this.enablePostButton();

            this.getFieldView('post').$element.prop('rows', 1);
        }
    }

    afterNotValid() {
        this.enablePostButton();
    }

    disablePostButton() {
        this.trigger('disable-post-button');

        this.$postButton.addClass('disable').attr('disabled', 'disabled');
    }

    enablePostButton() {
        this.trigger('enable-post-button');

        this.$postButton.removeClass('disable').removeAttr('disabled');
    }
}

export default EditStreamView;
PK]���$$views/stream/record/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/stream/record/list */

import ListExpandedRecordView from 'views/record/list-expanded';

/**
 * @property collection
 * @memberOf ListStreamRecordView#
 * @type module:collections/note
 */

class ListStreamRecordView extends ListExpandedRecordView {

    type = 'listStream'

    massActionsDisabled = true

    setup() {
        this.itemViews = this.getMetadata().get('clientDefs.Note.itemViews') || {};

        super.setup();

        this.isRenderingNew = false;

        this.listenTo(this.collection, 'sync', (c, r, options) => {
            if (!options.fetchNew) {
                return;
            }

            if (this.isRenderingNew) {
                // Prevent race condition.
                return;
            }

            let lengthBeforeFetch = options.lengthBeforeFetch || 0;

            if (lengthBeforeFetch === 0) {
                this.buildRows(() => this.reRender());

                return;
            }

            let $list = this.$el.find(this.listContainerEl);

            let rowCount = this.collection.length - lengthBeforeFetch;

            if (rowCount === 0) {
                return;
            }

            this.isRenderingNew = true;

            for (let i = rowCount - 1; i >= 0; i--) {
                let model = this.collection.at(i);

                this.buildRow(i, model, view => {
                    if (i === 0) {
                        this.isRenderingNew = false;
                    }

                    let $row = $(this.getRowContainerHtml(model.id));

                    // Prevent a race condition issue.
                    let $existingRow = this.$el.find(`[data-id="${model.id}"]`);

                    if ($existingRow.length) {
                        $row = $existingRow;
                    }

                    if (!$existingRow.length) {
                        $list.prepend($row);
                    }

                    view.render();
                });
            }
        });

        this.events['auxclick a[href][data-scope][data-id]'] = e => {
            let isCombination = e.button === 1 && (e.ctrlKey || e.metaKey);

            if (!isCombination) {
                return;
            }

            let $target = $(e.currentTarget);

            let id = $target.attr('data-id');
            let scope = $target.attr('data-scope');

            e.preventDefault();
            e.stopPropagation();

            this.actionQuickView({
                id: id,
                scope: scope,
            });
        };
    }

    buildRow(i, model, callback) {
        let key = model.id;

        this.rowList.push(key);

        let type = model.get('type');
        let viewName = this.itemViews[type] || 'views/stream/notes/' + Espo.Utils.camelCaseToHyphen(type);

        this.createView(key, viewName, {
            model: model,
            parentModel: this.model,
            acl: {
                edit: this.getAcl().checkModel(model, 'edit')
            },
            isUserStream: this.options.isUserStream,
            noEdit: this.options.noEdit,
            optionsToPass: ['acl'],
            name: this.type + '-' + model.entityType,
            selector: 'li[data-id="' + model.id + '"]',
            setViewBeforeCallback: this.options.skipBuildRows && !this.isRendered(),
        }, callback);
    }

    buildRows(callback) {
        this.checkedList = [];
        this.rowList = [];

        if (this.collection.length > 0) {
            this.wait(true);

            let count = this.collection.models.length;
            let built = 0;

            for (let i in this.collection.models) {
                let model = this.collection.models[i];

                this.buildRow(i, model, () => {
                    built++;

                    if (built === count) {
                        if (typeof callback === 'function') {
                            callback();
                        }

                        this.wait(false);

                        this.trigger('after:build-rows');
                    }
                });
            }

            return;
        }

        if (typeof callback === 'function') {
            callback();

            this.trigger('after:build-rows');
        }
    }

    showNewRecords() {
        this.collection.fetchNew();
    }
}

export default ListStreamRecordView;
PK]�{׏^^views/stream/panel.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import RelationshipPanelView from 'views/record/panels/relationship';
// noinspection ES6UnusedImports
import Textcomplete from 'jquery-textcomplete';

class PanelStreamView extends RelationshipPanelView {

    template = 'stream/panel'

    postingMode = false
    postDisabled = false
    relatedListFiltersDisabled = true
    layoutName = null
    filterList = ['all', 'posts', 'updates']

    additionalEvents = {
        /** @this PanelStreamView */
        'focus textarea[data-name="post"]': function () {
            this.enablePostingMode(true);
        },
        /** @this PanelStreamView */
        'click button.post': function () {
            this.post();
        },
        /** @this PanelStreamView */
        'click .action[data-action="switchInternalMode"]': function (e) {
            this.isInternalNoteMode = !this.isInternalNoteMode;

            var $a = $(e.currentTarget);

            if (this.isInternalNoteMode) {
                $a.addClass('enabled');
            } else {
                $a.removeClass('enabled');
            }

        },
        /** @this PanelStreamView */
        'keydown textarea[data-name="post"]': function (e) {
            if (Espo.Utils.getKeyFromKeyEvent(e) === 'Control+Enter') {
                e.stopPropagation();
                e.preventDefault();

                this.post();
            }

            // Don't hide to be able to focus on the upload button.
            /*if (e.code === 'Tab') {
                let $text = $(e.currentTarget);

                if ($text.val() === '') {
                    this.disablePostingMode();
                }
            }*/
        },
        /** @this PanelStreamView */
        'input textarea[data-name="post"]': function () {
            this.controlPreviewButton();
            this.controlPostButtonAvailability(this.$textarea.val());
        },
        /** @this PanelStreamView */
        'click .action[data-action="preview"]': function () {
            this.preview();
        },
    }

    data() {
        let data = super.data();

        data.postDisabled = this.postDisabled;
        data.placeholderText = this.placeholderText;
        data.allowInternalNotes = this.allowInternalNotes;

        return data;
    }

    controlPreviewButton() {
        this.$previewButton = this.$previewButton || this.$el.find('.stream-post-preview');

        if (this.$textarea.val() === '') {
            this.$previewButton.addClass('hidden');
        } else {
            this.$previewButton.removeClass('hidden');
        }
    }

    enablePostingMode(byFocus) {
        this.$el.find('.buttons-panel').removeClass('hide');

        if (!this.postingMode) {
            if (this.$textarea.val() && this.$textarea.val().length) {
                this.getView('postField').controlTextareaHeight();
            }

            var isClicked = false;

            $('body').on('click.stream-panel', (e) => {
                if (byFocus && !isClicked) {
                    isClicked = true;

                    return;
                }

                var $target = $(e.target);

                if ($target.parent().hasClass('remove-attachment')) {
                    return;
                }

                if ($.contains(this.$postContainer.get(0), e.target)) {
                    return;
                }

                if (this.$textarea.val() !== '') {
                    return;
                }

                if ($(e.target).closest('.popover-content').get(0)) {
                    return;
                }

                var attachmentsIds = this.seed.get('attachmentsIds') || [];

                if (
                    !attachmentsIds.length &&
                    (!this.getView('attachments') || !this.getView('attachments').isUploading)
                ) {
                    this.disablePostingMode();
                }
            });
        }

        this.postingMode = true;

        this.controlPreviewButton();
    }

    disablePostingMode() {
        this.postingMode = false;

        this.$textarea.val('');

        if (this.hasView('attachments')) {
            this.getView('attachments').empty();
        }

        this.$el.find('.buttons-panel').addClass('hide');

        $('body').off('click.stream-panel');

        this.$textarea.prop('rows', 1);
    }

    setup() {
        this.events = {
            ...this.additionalEvents,
            ...this.events,
        };

        this.scope = this.model.entityType;
        this.filter = this.getStoredFilter();

        this.setupTitle();

        this.placeholderText = this.translate('writeYourCommentHere', 'messages');
        this.allowInternalNotes = false;

        if (!this.getUser().isPortal()) {
            this.allowInternalNotes = this.getMetadata().get(['clientDefs', this.scope, 'allowInternalNotes']);
        }

        this.isInternalNoteMode = false;

        this.storageTextKey = 'stream-post-' + this.model.entityType + '-' + this.model.id;
        this.storageAttachmentsKey = 'stream-post-attachments-' + this.model.entityType + '-' + this.model.id;
        this.storageIsInernalKey = 'stream-post-is-internal-' + this.model.entityType + '-' + this.model.id;

        this.on('remove', () => {
            this.storeControl();

            $(window).off('beforeunload.stream-'+ this.cid);
        });

        $(window).off('beforeunload.stream-'+ this.cid);

        $(window).on('beforeunload.stream-'+ this.cid, () => {
            this.storeControl();
        });

        var storedAttachments = this.getSessionStorage().get(this.storageAttachmentsKey);

        this.setupActions();

        this.wait(true);

        this.getModelFactory().create('Note', (model) => {
            this.seed = model;

            if (storedAttachments) {
                this.hasStoredAttachments = true;
                this.seed.set({
                    attachmentsIds: storedAttachments.idList,
                    attachmentsNames: storedAttachments.names,
                    attachmentsTypes: storedAttachments.types,
                });
            }

            if (this.allowInternalNotes) {
                if (this.getMetadata().get(['entityDefs', 'Note', 'fields', 'isInternal', 'default'])) {
                    this.isInternalNoteMode = true;
                }

                if (this.getSessionStorage().has(this.storageIsInernalKey)) {
                    this.isInternalNoteMode = this.getSessionStorage().get(this.storageIsInernalKey);
                }
            }

            if (this.isInternalNoteMode) {
                this.seed.set('isInternal', true);
            }

            this.createView('postField', 'views/note/fields/post', {
                selector: '.textarea-container',
                name: 'post',
                mode: 'edit',
                params: {
                    required: true,
                    rowsMin: 1,
                    rows: 25,
                },
                model: this.seed,
                placeholderText: this.placeholderText,
                noResize: true,
            }, view => {
                this.initPostEvents(view);
            });

            this.createCollection(() => {
                this.wait(false);
            });

            this.listenTo(this.seed, 'change:attachmentsIds', () => {
                this.controlPostButtonAvailability();
            });
        });

        if (!this.defs.hidden) {
            this.subscribeToWebSocket();
        }

        this.once('show', () => {
            if (!this.isSubscribedToWebSocket) {
                this.subscribeToWebSocket();
            }
        });

        this.once('remove', () => {
            if (this.isSubscribedToWebSocket) {
                this.unsubscribeFromWebSocket();
            }
        });
    }

    subscribeToWebSocket() {
        if (!this.getHelper().webSocketManager) {
            return;
        }

        if (this.model.entityType === 'User') {
            return;
        }

        var topic = 'streamUpdate.' + this.model.entityType + '.' + this.model.id;
        this.streamUpdateWebSocketTopic = topic;

        this.isSubscribedToWebSocket = true;

        this.getHelper().webSocketManager.subscribe(topic, (t, data) => {
            if (data.createdById === this.getUser().id) {
                return;
            }

            if (data.noteId) {
                let model = this.collection.get(data.noteId);

                if (model) {
                    model.fetch();
                }

                return;
            }

            this.collection.fetchNew();
        });
    }

    unsubscribeFromWebSocket() {
        this.getHelper().webSocketManager.unsubscribe(this.streamUpdateWebSocketTopic);
    }

    setupTitle() {
        this.title = this.translate('Stream');

        this.titleHtml = this.title;

        if (this.filter && this.filter !== 'all') {
            this.titleHtml += ' &middot; ' + this.translate(this.filter, 'filters', 'Note');
        }
    }

    storeControl() {
        var isNotEmpty = false;

        if (this.$textarea && this.$textarea.length) {
            var text = this.$textarea.val();

            if (text.length) {
                this.getSessionStorage().set(this.storageTextKey, text);

                isNotEmpty = true;
            }
            else {
                if (this.hasStoredText) {
                    this.getSessionStorage().clear(this.storageTextKey);
                }
            }
        }

        var attachmentIdList = this.seed.get('attachmentsIds') || [];

        if (attachmentIdList.length) {
            this.getSessionStorage().set(this.storageAttachmentsKey, {
                idList: attachmentIdList,
                names: this.seed.get('attachmentsNames') || {},
                types: this.seed.get('attachmentsTypes') || {},
            });

            isNotEmpty = true;
        }
        else {
            if (this.hasStoredAttachments) {
                this.getSessionStorage().clear(this.storageAttachmentsKey);
            }
        }

        if (isNotEmpty) {
            this.getSessionStorage().set(this.storageIsInernalKey, this.isInternalNoteMode);
        }
        else {
            this.getSessionStorage().clear(this.storageIsInernalKey);
        }
    }

    createCollection(callback, context) {
        this.getCollectionFactory().create('Note', (collection) => {
            this.collection = collection;

            collection.url = this.model.entityType + '/' + this.model.id + '/stream';
            collection.maxSize = this.getConfig().get('recordsPerPageSmall') || 5;

            this.setFilter(this.filter);

            callback.call(context);
        });
    }

    initPostEvents(view) {
        this.listenTo(view, 'add-files', (files) => {
            this.getView('attachments').uploadFiles(files);

            if (!this.postingMode) {
                this.enablePostingMode();
            }
        });
    }

    afterRender() {
        this.$textarea = this.$el.find('textarea[data-name="post"]');
        this.$attachments = this.$el.find('div.attachments');
        this.$postContainer = this.$el.find('.post-container');
        this.$postButton = this.$el.find('button.post');

        let storedText = this.getSessionStorage().get(this.storageTextKey);

        if (storedText && storedText.length) {
            this.hasStoredText = true;
            this.$textarea.val(storedText);
        }

        this.controlPostButtonAvailability(storedText);

        if (this.isInternalNoteMode) {
            this.$el.find('.action[data-action="switchInternalMode"]').addClass('enabled');
        }

        let collection = this.collection;

        this.listenToOnce(collection, 'sync', () => {
            this.createView('list', 'views/stream/record/list', {
                selector: '> .list-container',
                collection: collection,
                model: this.model
            }, view => {
                view.render();
            });

            this.stopListening(this.model, 'all');
            this.stopListening(this.model, 'destroy');

            setTimeout(() => {
                this.listenTo(this.model, 'all', event => {
                    if (!~['sync', 'after:relate'].indexOf(event)) {
                        return;
                    }

                    collection.fetchNew();
                });

                this.listenTo(this.model, 'destroy', () => {
                    this.stopListening(this.model, 'all');
                });
            }, 500);
        });

        if (!this.defs.hidden) {
            collection.fetch();
        }
        else {
            this.once('show', () => collection.fetch());
        }

        let assignmentPermission = this.getAcl().getPermissionLevel('assignmentPermission');

        let buildUserListUrl = term => {
            var url = 'User?orderBy=name&limit=7&q=' + term + '&' + $.param({'primaryFilter': 'active'});

            if (assignmentPermission === 'team') {
                url += '&' + $.param({'boolFilterList': ['onlyMyTeam']})
            }

            return url;
        };

        if (assignmentPermission !== 'no') {
            this.$textarea.textcomplete([{
                match: /(^|\s)@(\w*)$/,
                index: 2,
                search: (term, callback) => {
                    if (term.length === 0) {
                        callback([]);

                        return;
                    }

                    Espo.Ajax
                        .getRequest(buildUserListUrl(term))
                        .then(data => callback(data.list));
                },
                template: (mention) => {
                    return this.getHelper()
                        .escapeString(mention.name) +
                        ' <span class="text-muted">@' +
                        this.getHelper().escapeString(mention.userName) + '</span>';
                },
                replace: (o) => {
                    return '$1@' + o.userName + '';
                },
            }]);

            this.once('remove', () => {
                if (this.$textarea.length) {
                    this.$textarea.textcomplete('destroy');
                }
            });
        }

        let $a = this.$el.find('.buttons-panel a.stream-post-info');

        let text1 = this.translate('infoMention', 'messages', 'Stream');
        let text2 = this.translate('infoSyntax', 'messages', 'Stream');

        let syntaxItemList = [
            ['code', '`{text}`'],
            ['multilineCode', '```{text}```'],
            ['strongText', '**{text}**'],
            ['emphasizedText', '*{text}*'],
            ['deletedText', '~~{text}~~'],
            ['blockquote', '> {text}'],
            ['link', '[{text}](url)'],
        ];

        let messageItemList = [];

        syntaxItemList.forEach(item => {
            let text = this.translate(item[0], 'syntaxItems', 'Stream');
            let result = item[1].replace('{text}', text);

            messageItemList.push(result);
        });

        let $ul = $('<ul>')
            .append(
                messageItemList.map(text => $('<li>').text(text))
            );

        let messageHtml =
            this.getHelper().transformMarkdownInlineText(text1) + '<br><br>' +
            this.getHelper().transformMarkdownInlineText(text2) + ':<br>' +
            $ul.get(0).outerHTML;

        Espo.Ui.popover($a, {
            content: messageHtml,
        }, this);

        this.createView('attachments', 'views/stream/fields/attachment-multiple', {
            model: this.seed,
            mode: 'edit',
            selector: 'div.attachments-container',
            defs: {
                name: 'attachments',
            },
        }, view => {
            view.render();
        });
    }

    afterPost() {
        this.$el.find('textarea.note').prop('rows', 1);
    }

    post() {
        let message = this.$textarea.val();

        this.disablePostButton();
        this.$textarea.prop('disabled', true);

        this.getModelFactory().create('Note', model => {
            if (this.getView('attachments').validateReady()) {
                this.$textarea.prop('disabled', false);
                this.enablePostButton();

                return;
            }

            if (message.trim() === '' && (this.seed.get('attachmentsIds') || []).length === 0) {
                this.notify('Post cannot be empty', 'error');
                this.$textarea.prop('disabled', false);
                this.controlPostButtonAvailability();

                this.$textarea.focus();

                return;
            }

            model.set('post', message);
            model.set('attachmentsIds', Espo.Utils.clone(this.seed.get('attachmentsIds') || []));
            model.set('type', 'Post');
            model.set('isInternal', this.isInternalNoteMode);

            this.prepareNoteForPost(model);

            Espo.Ui.notify(' ... ');

            model.save(null)
                .then(() => {
                    Espo.Ui.success(this.translate('Posted'));

                    this.collection.fetchNew();

                    this.$textarea.prop('disabled', false);
                    this.disablePostingMode();
                    this.afterPost();

                    if (this.getPreferences().get('followEntityOnStreamPost')) {
                        this.model.set('isFollowed', true);
                    }

                    this.getSessionStorage().clear(this.storageTextKey);
                    this.getSessionStorage().clear(this.storageAttachmentsKey);
                    this.getSessionStorage().clear(this.storageIsInernalKey);
                })
                .catch(() => {
                    this.$textarea.prop('disabled', false);
                    this.controlPostButtonAvailability();
                });
        });
    }

    prepareNoteForPost(model) {
        model.set('parentId', this.model.id);
        model.set('parentType', this.model.entityType);
    }

    getButtonList() {
        return [];
    }

    setupActions() {
        this.actionList = [];

        this.actionList.push({
            action: 'viewPostList',
            html:
                $('<span>')
                    .append(
                        $('<span>').text(this.translate('View List')),
                        ' &middot; ',
                        $('<span>').text(this.translate('posts', 'filters', 'Note')),
                    )
                    .get(0).innerHTML,
            onClick: () => this.actionViewPostList(),
        });

        this.actionList.push(false);

        this.filterList.forEach((item) => {
            let selected ;

            selected = item === 'all' ?
                !this.filter :
                item === this.filter;

            this.actionList.push({
                action: 'selectFilter',
                html:
                    $('<span>')
                        .append(
                            $('<span>')
                                .addClass('check-icon fas fa-check pull-right')
                                .addClass(!selected ? ' hidden' : ''),
                            $('<div>')
                                .text(this.translate(item, 'filters', 'Note')),
                        )
                        .get(0).innerHTML,
                data: {
                    name: item,
                },
            });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionViewPostList() {
        var url = this.model.entityType + '/' + this.model.id + '/posts';

        var data = {
            scope: 'Note',
            viewOptions: {
                url: url,
                title: this.translate('Stream') +
                    ' @right ' + this.translate('posts', 'filters', 'Note'),
                forceSelectAllAttributes: true,
            },
        };

        this.actionViewRelatedList(data);
    }

    getStoredFilter() {
        return this.getStorage().get('state', 'streamPanelFilter' + this.scope) || null;
    }

    storeFilter(filter) {
        if (filter) {
            this.getStorage().set('state', 'streamPanelFilter' + this.scope, filter);
        }
        else {
            this.getStorage().clear('state', 'streamPanelFilter' + this.scope);
        }
    }

    setFilter(filter) {
        this.filter = filter;
        this.collection.data.filter = null;

        if (filter) {
            this.collection.data.filter = filter;
        }
    }

    actionRefresh() {
        if (this.hasView('list')) {
            this.getView('list').showNewRecords();
        }
    }

    preview() {
        this.createView('dialog', 'views/modal', {
            templateContent: '<div class="complex-text">' +
                   '{{complexText viewObject.options.text linksInNewTab=true}}</div>',
            text: this.$textarea.val(),
            headerText: this.translate('Preview'),
            backdrop: true,
        }, view => {
            view.render();
        });
    }

    controlPostButtonAvailability(postEntered) {
        let attachmentsIdList = this.seed.get('attachmentsIds') || [];
        let post = this.seed.get('post');

        if (typeof postEntered !== 'undefined') {
            post = postEntered;
        }

        let isEmpty = !post && !attachmentsIdList.length;

        if (isEmpty) {
            if (this.$postButton.hasClass('disabled')) {
                return;
            }

            this.disablePostButton();

            return;
        }

        if (!this.$postButton.hasClass('disabled')) {
            return;
        }

        this.enablePostButton();
    }

    disablePostButton() {
        this.$postButton.addClass('disabled').attr('disabled', 'disabled');
    }

    enablePostButton() {
        this.$postButton.removeClass('disabled').removeAttr('disabled');
    }
}

export default PanelStreamView;
PK]
�cvvviews/stream/notes/update.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import NoteStreamView from 'views/stream/note';

class UpdateNoteStreamView extends NoteStreamView {

    template = 'stream/notes/update'
    messageName = 'update'

    data() {
        return {
            ...super.data(),
            fieldsArr: this.fieldsArr,
            parentType: this.model.get('parentType'),
        };
    }

    init() {
        if (this.getUser().isAdmin()) {
            this.isRemovable = true;
        }

        super.init();
    }

    setup() {
        this.addActionHandler('expandDetails', (e, target) => this.toggleDetails(e, target));

        this.createMessage();

        this.wait(true);

        this.getModelFactory().create(this.model.get('parentType'), model => {
            let modelWas = model;
            let modelBecame = model.clone();

            let data = this.model.get('data');

            data.attributes = data.attributes || {};

            modelWas.set(data.attributes.was);
            modelBecame.set(data.attributes.became);

            this.fieldsArr = [];

            let fields = data.fields;

            fields.forEach(field => {
                let type = model.getFieldType(field) || 'base';
                let viewName = this.getMetadata().get(['entityDefs', model.entityType, 'fields', field, 'view']) ||
                    this.getFieldManager().getViewName(type);

                let attributeList = this.getFieldManager().getEntityTypeFieldAttributeList(model.entityType, field);

                let hasValue = false;

                for (let attribute of attributeList) {
                    if (attribute in data.attributes.was) {
                        hasValue = true;

                        break;
                    }
                }

                if (!hasValue) {
                    this.fieldsArr.push({
                        field: field,
                        noValues: true,
                    });

                    return;
                }

                this.createView(field + 'Was', viewName, {
                    model: modelWas,
                    readOnly: true,
                    defs: {
                        name: field
                    },
                    mode: 'detail',
                    inlineEditDisabled: true,
                });

                this.createView(field + 'Became', viewName, {
                    model: modelBecame,
                    readOnly: true,
                    defs: {
                        name: field,
                    },
                    mode: 'detail',
                    inlineEditDisabled: true,
                });

                this.fieldsArr.push({
                    field: field,
                    was: field + 'Was',
                    became: field + 'Became',
                });
            });

            this.wait(false);
        });
    }

    /**
     * @param {MouseEvent} event
     * @param {HTMLElement} target
     */
    toggleDetails(event, target) {
        if (this.$el.find('.details').hasClass('hidden')) {
            this.$el.find('.details').removeClass('hidden');

            $(target).find('span')
                .removeClass('fa-chevron-down')
                .addClass('fa-chevron-up');

            return;
        }

        this.$el.find('.details').addClass('hidden');

        $(target).find('span')
            .addClass('fa-chevron-down')
            .removeClass('fa-chevron-up');
    }
}

export default UpdateNoteStreamView;
PK]��views/stream/notes/post.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import NoteStreamView from 'views/stream/note';

class PostNoteStreamView extends NoteStreamView {

    template = 'stream/notes/post'
    messageName = 'post'
    isEditable = true
    isRemovable = true

    data() {
        let data = super.data();

        data.showAttachments = !!(this.model.get('attachmentsIds') || []).length;
        data.showPost = !!this.model.get('post');
        data.isInternal = this.isInternal;

        return data;
    }

    setup() {
        this.createField('post', null, null, 'views/stream/fields/post');

        this.createField('attachments', 'attachmentMultiple', {}, 'views/stream/fields/attachment-multiple', {
            previewSize: this.options.isNotification ? 'small' : 'medium'
        });

        this.isInternal = this.model.get('isInternal');

        if (!this.model.get('post') && this.model.get('parentId')) {
            this.messageName = 'attach';

            if (this.isThis) {
                this.messageName += 'This';
            }
        }

        this.listenTo(this.model, 'change', () => {
            if (this.model.hasChanged('post') || this.model.hasChanged('attachmentsIds')) {
                this.reRender();
            }
        });

        if (this.model.get('parentId')) {
            this.createMessage();

            return;
        }

        if (this.model.get('isGlobal')) {
            this.messageName = 'postTargetAll';
            this.createMessage();

            return;
        }

        if (this.model.has('teamsIds') && this.model.get('teamsIds').length) {
            let teamIdList = this.model.get('teamsIds');
            let teamNameHash = this.model.get('teamsNames') || {};
            this.messageName = 'postTargetTeam';

            if (teamIdList.length > 1) {
                this.messageName = 'postTargetTeams';
            }

            let teamHtmlList = [];

            teamIdList.forEach(teamId => {
                let teamName = teamNameHash[teamId];

                if (!teamName) {
                    return;
                }

                teamHtmlList.push(
                    $('<a>')
                        .attr('href', '#Team/view/' + teamId)
                        .text(teamName)
                        .get(0).outerHTML
                );
            });

            this.messageData['html:target'] = teamHtmlList.join(', ');

            this.createMessage();

            return;
        }

        if (this.model.has('portalsIds') && this.model.get('portalsIds').length) {
            let portalIdList = this.model.get('portalsIds');
            let portalNameHash = this.model.get('portalsNames') || {};

            this.messageName = 'postTargetPortal';

            if (portalIdList.length > 1) {
                this.messageName = 'postTargetPortals';
            }

            let portalHtmlList = [];

            portalIdList.forEach(portalId =>{
                let portalName = portalNameHash[portalId];

                if (!portalName) {
                    return;
                }

                portalHtmlList.push(
                    $('<a>')
                        .attr('href', '#Portal/view/' + portalId)
                        .text(portalName)
                        .get(0).outerHTML
                )
            });

            this.messageData['html:target'] = portalHtmlList.join(', ');

            this.createMessage();

            return;
        }

        if (!this.model.has('usersIds') || !this.model.get('usersIds').length) {
            this.createMessage();

            return;
        }

        let userIdList = this.model.get('usersIds');
        let userNameHash = this.model.get('usersNames') || {};

        this.messageName = 'postTarget';

        if (userIdList.length === 1 && userIdList[0] === this.model.get('createdById')) {
            this.messageName = 'postTargetSelf';
            this.createMessage();

            return;
        }

        let userHtmlList = [];

        userIdList.forEach(userId => {
            if (userId === this.getUser().id) {
                this.messageName = 'postTargetYou';

                if (userIdList.length > 1) {
                    if (userId === this.model.get('createdById')) {
                        this.messageName = 'postTargetSelfAndOthers';
                    } else {
                        this.messageName = 'postTargetYouAndOthers';
                    }
                }

                return;
            }

            if (userId === this.model.get('createdById')) {
                this.messageName = 'postTargetSelfAndOthers';

                return;
            }

            let userName = userNameHash[userId];

            if (!userName) {
                return;
            }

            userHtmlList.push(
                $('<a>')
                    .attr('href', '#User/view/' + userId)
                    .attr('data-scope', 'User')
                    .attr('data-id', userId)
                    .text(userName)
                    .get(0).outerHTML
            );
        });

        this.messageData['html:target'] = userHtmlList.join(', ');

        this.createMessage();
    }
}

export default PostNoteStreamView;
PK]�0��
�
views/stream/notes/create.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import NoteStreamView from 'views/stream/note';

class CreateNoteStreamView extends NoteStreamView {

    template = 'stream/notes/create'
    assigned = false
    messageName = 'create'
    isRemovable = false

    data() {
        return {
            ...super.data(),
            statusText: this.statusText,
            statusStyle: this.statusStyle,
        };
    }

    setup() {
        if (this.model.get('data')) {
            this.setupData();
        }

        this.createMessage();
    }

    setupData() {
        let data = /** @type Object.<string, *> */this.model.get('data');

        this.assignedUserId = data.assignedUserId || null;
        this.assignedUserName = data.assignedUserName || null;

        this.messageData['assignee'] =
            $('<a>')
                .attr('href', '#User/view/' + this.assignedUserId)
                .text(this.assignedUserName);

        let isYou = false;

        if (this.isUserStream) {
            if (this.assignedUserId === this.getUser().id) {
                isYou = true;
            }
        }

        if (this.assignedUserId) {
            this.messageName = 'createAssigned';

            if (this.isThis) {
                this.messageName += 'This';

                if (this.assignedUserId === this.model.get('createdById')) {
                    this.messageName += 'Self';
                }
            } else {
                if (this.assignedUserId === this.model.get('createdById')) {
                    this.messageName += 'Self';
                }
                else if (isYou) {
                    this.messageName += 'You';
                }
            }
        }

        if (data.statusField) {
            let statusField = this.statusField = data.statusField;
            let statusValue = data.statusValue;

            this.statusStyle = data.statusStyle || 'default';
            this.statusText = this.getLanguage()
                .translateOption(statusValue, statusField, this.model.get('parentType'));
        }
    }
}

export default CreateNoteStreamView;

PK]��� 	 	views/stream/notes/status.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import NoteStreamView from 'views/stream/note';

class StatusNoteStreamView extends NoteStreamView {

    template = 'stream/notes/status'
    messageName = 'status'

    data() {
        return {
            ...super.data(),
            style: this.style,
            statusText: this.statusText,
        };
    }

    init() {
        if (this.getUser().isAdmin()) {
            this.isRemovable = true;
        }

        super.init();
    }

    setup() {
        let data = this.model.get('data');

        let field = data.field;
        let value = data.value;

        this.style = data.style || 'default';
        this.statusText = this.getLanguage().translateOption(value, field, this.model.get('parentType'));

        this.messageData['field'] = this.translate(field, 'fields', this.model.get('parentType')).toLowerCase();

        this.createMessage();
    }
}

export default StatusNoteStreamView;
PK]���ddviews/stream/notes/assign.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import NoteStreamView from 'views/stream/note';

class AssignNoteStreamView extends NoteStreamView {

    template = 'stream/notes/assign'
    messageName = 'assign'

    init() {
        if (this.getUser().isAdmin()) {
            this.isRemovable = true;
        }

        super.init();
    }

    setup() {
        let data = this.model.get('data');

        this.assignedUserId = data.assignedUserId || null;
        this.assignedUserName = data.assignedUserName || null;

        this.messageData['assignee'] =
            $('<a>')
                .attr('href', '#User/view/' + data.assignedUserId)
                .text(data.assignedUserName);

        if (this.isUserStream) {
            if (this.assignedUserId) {
                if (this.assignedUserId === this.model.get('createdById')) {
                    this.messageName += 'Self';
                } else {
                    if (this.assignedUserId === this.getUser().id) {
                        this.messageName += 'You';
                    }
                }
            } else {
                this.messageName += 'Void';
            }
        } else {
            if (this.assignedUserId) {
                if (this.assignedUserId === this.model.get('createdById')) {
                    this.messageName += 'Self';
                }
            } else {
                this.messageName += 'Void';
            }
        }

        this.createMessage();
    }
}

export default AssignNoteStreamView;
PK]�J?TR
R
$views/stream/notes/create-related.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import NoteStreamView from 'views/stream/note';

class CreateRelatedNoteStreamView extends NoteStreamView {

    template = 'stream/notes/create-related'
    messageName = 'createRelated'

    data() {
        return {
            ...super.data(),
            relatedTypeString: this.translateEntityType(this.entityType),
            iconHtml: this.getIconHtml(this.entityType, this.entityId),
        };
    }

    init() {
        if (this.getUser().isAdmin()) {
            this.isRemovable = true;
        }

        super.init();
    }

    setup() {
        let data = this.model.get('data') || {};

        this.entityType = this.model.get('relatedType') || data.entityType || null;
        this.entityId = this.model.get('relatedId') || data.entityId || null;
        this.entityName = this.model.get('relatedName') ||  data.entityName || null;

        this.messageData['relatedEntityType'] = this.translateEntityType(this.entityType);

        this.messageData['relatedEntity'] =
            $('<a>')
                .attr('href', '#' + this.entityType + '/view/' + this.entityId)
                .text(this.entityName);

        this.createMessage();
    }
}

export default CreateRelatedNoteStreamView;
PK]\���33$views/stream/notes/email-received.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import NoteStreamView from 'views/stream/note';

class EmailReceivedNoteStreamView extends NoteStreamView {

    template = 'stream/notes/email-received'
    isRemovable = false
    isSystemAvatar = true

    data() {
        return {
            ...super.data(),
            emailId: this.emailId,
            emailName: this.emailName,
            hasPost: this.hasPost,
            hasAttachments: this.hasAttachments,
            emailIconClassName: this.getMetadata().get(['clientDefs', 'Email', 'iconClass']) || '',
        };
    }

    setup() {
        let data = /** @type Object.<string, *> */this.model.get('data') || {};

        this.emailId = data.emailId;
        this.emailName = data.emailName;

        if (
            this.parentModel &&
            (
                this.model.get('parentType') === this.parentModel.entityType &&
                this.model.get('parentId') === this.parentModel.id
            )
        ) {
            if (this.model.get('post')) {
                this.createField('post', null, null, 'views/stream/fields/post');
                this.hasPost = true;
            }

            if ((this.model.get('attachmentsIds') || []).length) {
                this.createField(
                    'attachments',
                    'attachmentMultiple',
                    {},
                    'views/stream/fields/attachment-multiple'
                );

                this.hasAttachments = true;
            }
        }

        this.messageData['email'] =
            $('<a>')
                .attr('href', '#Email/view/' + data.emailId)
                .text(data.emailName);

        this.messageName = 'emailReceived';

        if (data.isInitial) {
            this.messageName += 'Initial';
        }

        if (data.personEntityId) {
            this.messageName += 'From';

            this.messageData['from'] =
                $('<a>')
                    .attr('href', '#' + data.personEntityType + '/view/' + data.personEntityId)
                    .text(data.personEntityName);
        }

        if (
            this.model.get('parentType') === data.personEntityType &&
            this.model.get('parentId') === data.personEntityId
        ) {
            this.isThis = true;
        }

        if (this.isThis) {
            this.messageName += 'This';
        }

        this.createMessage();
    }
}

export default EmailReceivedNoteStreamView;
PK]�,/=
=
views/stream/notes/relate.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import NoteStreamView from 'views/stream/note';

class RelateNoteStreamView extends NoteStreamView {

    template = 'stream/notes/create-related'
    messageName = 'relate'

    data() {
        return {
            ...super.data(),
            relatedTypeString: this.translateEntityType(this.entityType),
            iconHtml: this.getIconHtml(this.entityType, this.entityId),
        };
    }

    init() {
        if (this.getUser().isAdmin()) {
            this.isRemovable = true;
        }

        super.init();
    }

    setup() {
        let data = this.model.get('data') || {};

        this.entityType = this.model.get('relatedType') || data.entityType || null;
        this.entityId = this.model.get('relatedId') || data.entityId || null;
        this.entityName = this.model.get('relatedName') ||  data.entityName || null;

        this.messageData['relatedEntityType'] = this.translateEntityType(this.entityType);

        this.messageData['relatedEntity'] =
            $('<a>')
                .attr('href', '#' + this.entityType + '/view/' + this.entityId)
                .text(this.entityName);

        this.createMessage();
    }
}

export default RelateNoteStreamView;
PK]zQ �bbviews/stream/notes/unrelate.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import RelateNoteStreamView from 'views/stream/notes/relate';

class UnrelateNoteStreamView extends RelateNoteStreamView {

    template = 'stream/notes/create-related'
    messageName = 'unrelate'
}

export default UnrelateNoteStreamView;
PK]���.

 views/stream/notes/email-sent.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import NoteStreamView from 'views/stream/note';

class EmailSentNoteStreamView extends NoteStreamView {

    template = 'stream/notes/email-sent'
    isRemovable = false

    data() {
        return {
            ...super.data(),
            emailId: this.emailId,
            emailName: this.emailName,
            hasPost: this.hasPost,
            hasAttachments: this.hasAttachments,
            emailIconClassName: this.getMetadata().get(['clientDefs', 'Email', 'iconClass']) || '',
        };
    }

    setup() {
        let data = /** @type Object.<string, *> */this.model.get('data') || {};

        this.emailId = data.emailId;
        this.emailName = data.emailName;

        if (
            this.parentModel &&
            (
                this.model.get('parentType') === this.parentModel.entityType &&
                this.model.get('parentId') === this.parentModel.id
            )
        ) {
            if (this.model.get('post')) {
                this.createField('post', null, null, 'views/stream/fields/post');
                this.hasPost = true;
            }

            if ((this.model.get('attachmentsIds') || []).length) {
                this.createField('attachments', 'attachmentMultiple', {}, 'views/stream/fields/attachment-multiple');
                this.hasAttachments = true;
            }
        }

        this.messageData['email'] =
            $('<a>')
                .attr('href', '#Email/view/' + data.emailId)
                .text(data.emailName);

        this.messageName = 'emailSent';

        this.messageData['by'] =
            $('<a>')
                .attr('href', '#' + data.personEntityType + '/view/' + data.personEntityId)
                .text(data.personEntityName);

        if (this.isThis) {
            this.messageName += 'This';
        }

        this.createMessage();
    }
}

export default EmailSentNoteStreamView;
PK]O4�++%views/stream/notes/mention-in-post.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import NoteStreamView from 'views/stream/note';

class MentionInPostNoteStreamView extends NoteStreamView {

    template = 'stream/notes/post'
    messageName = 'mentionInPost'

    data() {
        return {
            ...super.data(),
            showAttachments: !!(this.model.get('attachmentsIds') || []).length,
            showPost: !!this.model.get('post'),
        };
    }

    setup() {
        if (this.model.get('post')) {
            this.createField('post', null, null, 'views/stream/fields/post');
        }

        if ((this.model.get('attachmentsIds') || []).length) {
            this.createField('attachments', 'attachmentMultiple', {}, 'views/stream/fields/attachment-multiple', {
                previewSize: this.options.isNotification ? 'small' : null
            });
        }

        this.messageData['mentioned'] = this.options.userId;

        if (!this.model.get('parentId')) {
            this.messageName = 'mentionInPostTarget';
        }

        if (!this.isUserStream || this.options.userId !== this.getUser().id) {
            this.createMessage();

            return;
        }

        if (this.model.get('parentId')) {
            this.messageName = 'mentionYouInPost';

            this.createMessage();

            return;
        }

        this.messageName = 'mentionYouInPostTarget';

        if (this.model.get('isGlobal')) {
            this.messageName = 'mentionYouInPostTargetAll';

            this.createMessage();

            return;
        }

        this.messageName = 'mentionYouInPostTarget';

        if (this.model.has('teamsIds') && this.model.get('teamsIds').length) {
            let teamIdList = this.model.get('teamsIds');
            let teamNameHash = this.model.get('teamsNames') || {};

            let teamHtmlList = [];

            teamIdList.forEach(teamId => {
                let teamName = teamNameHash[teamId];

                if (!teamName) {
                    return;
                }

                teamHtmlList.push(
                    $('<a>')
                        .attr('href', '#Team/view/' + teamId)
                        .text(teamName)
                        .get(0).outerHTML
                );
            });

            this.messageData['html:target'] = teamHtmlList.join(', ');

            this.createMessage();

            return;
        }

        if (this.model.has('usersIds') && this.model.get('usersIds').length) {
            var userIdList = this.model.get('usersIds');
            var userNameHash = this.model.get('usersNames') || {};

            if (userIdList.length === 1 && userIdList[0] === this.model.get('createdById')) {
                this.messageName = 'mentionYouInPostTargetNoTarget';
                this.createMessage();

                return;
            }

            let userHtmlList = [];

            userIdList.forEach(userId => {
                let userName = userNameHash[userId];

                if (!userName) {
                    return;
                }

                userHtmlList.push(
                    $('<a>')
                        .attr('href', '#User/view/' + userId)
                        .text(userName)
                        .get(0).outerHTML
                );
            });

            this.messageData['html:target'] = userHtmlList.join(', ');

            this.createMessage();

            return;
        }

        if (this.model.get('targetType') === 'self') {
            this.messageName = 'mentionYouInPostTargetNoTarget';
        }

        this.createMessage();
    }
}

// noinspection JSUnusedGlobalSymbols
export default MentionInPostNoteStreamView;
PK]���{��"views/stream/modals/create-post.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ModalView from 'views/modal';

class CreatePostModalView extends ModalView {

    templateContent = '<div class="record">{{{record}}}</div>'

    shortcutKeys = {
        'Control+Enter': 'post',
    }

    setup() {
        this.headerText = this.translate('Create Post');

        this.buttonList = [
            {
                name: 'post',
                label: 'Post',
                style: 'primary',
                title: 'Ctrl+Enter',
                onClick: () => this.post(),
            },
            {
                name: 'cancel',
                label: 'Cancel',
                title: 'Esc',
                onClick: dialog => {
                    dialog.close();
                },
            }
        ];

        this.wait(true);

        this.getModelFactory().create('Note', model => {
            this.createView('record', 'views/stream/record/edit', {
                model: model,
                selector: '.record',
            }, view => {
                this.listenTo(view, 'after:save', () => {
                    this.trigger('after:save');
                });

                this.listenTo(view, 'disable-post-button', () => this.disableButton('post'));
                this.listenTo(view, 'enable-post-button', () => this.enableButton('post'));
            });

            this.wait(false);
        });
    }

    /**
     * @return {module:views/record/edit}
     */
    getRecordView() {
        return this.getView('record');
    }

    post() {
        this.getRecordView().save();
    }
}

export default CreatePostModalView;
PK]���}uHuH
views/main.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/main */

import View from 'view';

/**
 * A base main view. The detail, edit, list views to be extended from.
 */
class MainView extends View {

    /**
     * A scope name.
     *
     * @type {string} scope
     */
    scope = ''

    /**
     * A name.
     *
     * @type {string} name
     */
    name = ''

    /**
     * A top-right menu item (button or dropdown action).
     * Handled by a class method `action{Action}`, a click handler or a handler class.
     *
     * @typedef {Object} module:views/main~MenuItem
     *
     * @property {string} [name] A name.
     * @property {string} [action] An action.
     * @property {string} [link] A link.
     * @property {string} [label] A translatable label.
     * @property {string} [labelTranslation] A label translation path.
     * @property {'default'|'danger'|'success'|'warning'} [style] A style. Only for buttons.
     * @property {boolean} [hidden]
     * @property {Object.<string,string|number|boolean>} [data] Data attribute values.
     * @property {string} [title] A title.
     * @property {string} [iconHtml] An icon HTML.
     * @property {string} [iconClass] An icon class.
     * @property {string} [html] An HTML.
     * @property {string} [text] A text.
     * @property {string} [className] An additional class name. Only for buttons.
     * @property {'create'|'read'|'edit'|'stream'|'delete'} [acl] Access to a record (or a scope if `aclScope` specified)
     *   required for a menu item.
     * @property {string} [aclScope] A scope to check access to with the `acl` parameter.
     * @property {string} [configCheck] A config parameter defining a menu item availability.
     *   If starts with `!`, then the result is negated.
     * @property {module:utils~AccessDefs[]} [accessDataList] Access definitions.
     * @property {string} [initFunction] An init function.
     * @property {function()} [onClick] A click handler.
     */

    /**
     * Top-right menu definitions.
     *
     * @type {{
     *     buttons: module:views/main~MenuItem[],
     *     dropdown: module:views/main~MenuItem[],
     *     actions: module:views/main~MenuItem[],
     * }} menu
     * @private
     * @internal
     */
    menu = {}

    /**
     * @private
     * @type {JQuery|null}
     */
    $headerActionsContainer = null

    /**
     * A shortcut-key => action map.
     *
     * @protected
     * @type {?Object.<string,string|function (JQueryKeyEventObject): void>}
     */
    shortcutKeys = null

    /** @inheritDoc */
    events = {
        /** @this MainView */
        'click .action': function (e) {
            Espo.Utils.handleAction(this, e.originalEvent, e.currentTarget, {
                actionItems: [...this.menu.buttons, ...this.menu.dropdown],
                className: 'main-header-manu-action',
            });
        },
    }

    lastUrl

    /** @inheritDoc */
    init() {
        this.scope = this.options.scope || this.scope;
        this.menu = {};

        this.options.params = this.options.params || {};

        if (this.name && this.scope) {
            let key = this.name.charAt(0).toLowerCase() + this.name.slice(1);

            this.menu = this.getMetadata().get(['clientDefs', this.scope, 'menu', key]) || {};
        }

        /**
         * @private
         * @type {string[]}
         */
        this.headerActionItemTypeList = ['buttons', 'dropdown', 'actions'];

        this.menu = Espo.Utils.cloneDeep(this.menu);

        let globalMenu = {};

        if (this.name) {
            globalMenu = Espo.Utils.cloneDeep(
                this.getMetadata()
                    .get(['clientDefs', 'Global', 'menu',
                        this.name.charAt(0).toLowerCase() + this.name.slice(1)]) || {}
            );
        }

        this.headerActionItemTypeList.forEach(type => {
            this.menu[type] = this.menu[type] || [];
            this.menu[type] = this.menu[type].concat(globalMenu[type] || []);

            let itemList = this.menu[type];

            itemList.forEach(item => {
                let viewObject = this;

                if (item.initFunction && item.data.handler) {
                    this.wait(new Promise(resolve => {
                        Espo.loader.require(item.data.handler, Handler => {
                            let handler = new Handler(viewObject);

                            handler[item.initFunction].call(handler);

                            resolve();
                        });
                    }));
                }
            });
        });

        this.updateLastUrl();

        this.on('after:render-internal', () => {
            this.$headerActionsContainer = this.$el.find('.page-header .header-buttons');
        });

        this.on('header-rendered', () => {
            this.$headerActionsContainer = this.$el.find('.page-header .header-buttons');

            this.adjustButtons();
        });

        this.on('after:render', () => this.adjustButtons());

        if (this.shortcutKeys) {
            this.shortcutKeys = Espo.Utils.cloneDeep(this.shortcutKeys);
        }
    }

    setupFinal() {
        if (this.shortcutKeys) {
            this.events['keydown.main'] = e => {
                let key = Espo.Utils.getKeyFromKeyEvent(e);

                if (typeof this.shortcutKeys[key] === 'function') {
                    this.shortcutKeys[key].call(this, e.originalEvent);

                    return;
                }

                let actionName = this.shortcutKeys[key];

                if (!actionName) {
                    return;
                }

                e.preventDefault();
                e.stopPropagation();

                let methodName = 'action' + Espo.Utils.upperCaseFirst(actionName);

                if (typeof this[methodName] === 'function') {
                    this[methodName]();

                    return;
                }

                this[actionName]();
            };
        }
    }

    /**
     * Update a last history URL.
     */
    updateLastUrl() {
        this.lastUrl = this.getRouter().getCurrentUrl();
    }

    /**
     * @internal
     * @returns {{
     *     buttons?: module:views/main~MenuItem[],
     *     dropdown?: module:views/main~MenuItem[],
     *     actions?: module:views/main~MenuItem[],
     * }}
     */
    getMenu() {
        if (this.menuDisabled || !this.menu) {
            return {};
        }

        let menu = {};

        this.headerActionItemTypeList.forEach(type => {
            (this.menu[type] || []).forEach(item => {
                if (item === false) {
                    menu[type].push(false);

                    return;
                }

                item = Espo.Utils.clone(item);

                menu[type] = menu[type] || [];

                if (!Espo.Utils.checkActionAvailability(this.getHelper(), item)) {
                    return;
                }

                if (!Espo.Utils.checkActionAccess(this.getAcl(), this.model || this.scope, item)) {
                    return;
                }

                if (item.accessDataList) {
                    if (!Espo.Utils
                        .checkAccessDataList(item.accessDataList, this.getAcl(), this.getUser())
                    ) {
                        return;
                    }
                }

                item.name = item.name || item.action;
                item.action = item.action || null;

                if (item.labelTranslation) {
                    item.html = this.getHelper().escapeString(
                        this.getLanguage().translatePath(item.labelTranslation)
                    );
                }

                menu[type].push(item);
            });
        });

        return menu;
    }

    /**
     * Get a header HTML. To be overridden.
     *
     * @returns {string} HTML.
     */
    getHeader() {
        return '';
    }

    /**
     * Build a header HTML. To be called from the #getHeader method.
     * Beware of XSS.
     *
     * @param {(string|Element|JQuery)[]} itemList A breadcrumb path. Like: Account > Name > edit.
     * @returns {string} HTML
     */
    buildHeaderHtml(itemList) {
        let $itemList = itemList.map(item => {
            return $('<div>')
                .addClass('breadcrumb-item')
                .append(item);
        });

        let $div = $('<div>')
            .addClass('header-breadcrumbs');

        $itemList.forEach(($item, i) => {
            $div.append($item);

            if (i === $itemList.length - 1) {
                return;
            }

            $div.append(
                $('<div>')
                    .addClass('breadcrumb-separator')
                    .append(
                        $('<span>').addClass('chevron-right')
                    )
            )
        });

        return $div.get(0).outerHTML;
    }


    /**
     * Get an icon HTML.
     *
     * @returns {string} HTML
     */
    getHeaderIconHtml() {
        return this.getHelper().getScopeColorIconHtml(this.scope);
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * Action 'showModal'.
     *
     * @todo Revise. To be removed?
     *
     * @param {Object} data
     */
    actionShowModal(data) {
        let view = data.view;

        if (!view) {
            return;
        }

        this.createView('modal', view, {
            model: this.model,
            collection: this.collection,
        }, view => {
            view.render();

            this.listenTo(view, 'after:save', () => {
                if (this.model) {
                    this.model.fetch();
                }

                if (this.collection) {
                    this.collection.fetch();
                }
            });
        });
    }

    /**
     * Add a menu item.
     *
     * @param {'buttons'|'dropdown'} type A type.
     * @param {module:views/main~MenuItem|false} item Item definitions.
     * @param {boolean} [toBeginning=false] To beginning.
     * @param {boolean} [doNotReRender=false] Skip re-render.
     */
    addMenuItem(type, item, toBeginning, doNotReRender) {
        if (item) {
            item.name = item.name || item.action || Espo.Utils.generateId();

            let name = item.name;

            let index = -1;

            this.menu[type].forEach((data, i) => {
                data = data || {};

                if (data.name === name) {
                    index = i;
                }
            });

            if (~index) {
                this.menu[type].splice(index, 1);
            }
        }

        let method = 'push';

        if (toBeginning) {
            method  = 'unshift';
        }

        this.menu[type][method](item);

        if (!doNotReRender && this.isRendered()) {
            this.getHeaderView().reRender();

            return;
        }

        if (!doNotReRender && this.isBeingRendered()) {
            this.once('after:render', () => {
                this.getHeaderView().reRender();
            });
        }
    }

    /**
     * Remove a menu item.
     *
     * @param {string} name An item name.
     * @param {boolean} [doNotReRender] Skip re-render.
     */
    removeMenuItem(name, doNotReRender) {
        let index = -1;
        let type = false;

        this.headerActionItemTypeList.forEach(t => {
            (this.menu[t] || []).forEach((item, i) => {
                item = item || {};

                if (item.name === name) {
                    index = i;
                    type = t;
                }
            });
        });

        if (~index && type) {
            this.menu[type].splice(index, 1);
        }

        if (!doNotReRender && this.isRendered()) {
            this.getHeaderView().reRender();

            return;
        }

        if (!doNotReRender && this.isBeingRendered()) {
            this.once('after:render', () => {
                this.getHeaderView().reRender();

            });

            return;
        }

        if (doNotReRender && this.isRendered()) {
            this.$headerActionsContainer.find('[data-name="' + name + '"]').remove();
        }
    }

    /**
     * Disable a menu item.
     *
     * @param {string} name A name.
     */
    disableMenuItem(name) {
        this.$headerActionsContainer
            .find('[data-name="' + name + '"]')
            .addClass('disabled')
            .attr('disabled');
    }

    /**
     * Enable a menu item.
     *
     * @param {string} name A name.
     */
    enableMenuItem(name) {
        this.$headerActionsContainer
            .find('[data-name="' + name + '"]')
            .removeClass('disabled')
            .removeAttr('disabled');
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * Action 'navigateToRoot'.
     *
     * @param {Object} data
     * @param {MouseEvent} event
     */
    actionNavigateToRoot(data, event) {
        event.stopPropagation();

        this.getRouter().checkConfirmLeaveOut(() => {
            let options = {
                isReturn: true,
            };

            let rootUrl = this.options.rootUrl || this.options.params.rootUrl || '#' + this.scope;

            this.getRouter().navigate(rootUrl, {trigger: false});
            this.getRouter().dispatch(this.scope, null, options);
        });
    }

    /**
     * Hide a menu item.
     *
     * @param {string} name A name.
     */
    hideHeaderActionItem(name) {
        this.headerActionItemTypeList.forEach(t => {
            (this.menu[t] || []).forEach(item => {
                item = item || {};

                if (item.name === name) {
                    item.hidden = true;
                }
            });
        });

        if (!this.isRendered()) {
            return;
        }

        this.$headerActionsContainer.find('li > .action[data-name="'+name+'"]').parent().addClass('hidden');
        this.$headerActionsContainer.find('a.action[data-name="'+name+'"]').addClass('hidden');

        this.controlMenuDropdownVisibility();
        this.adjustButtons();
    }

    /**
     * Show a hidden menu item.
     *
     * @param {string} name A name.
     */
    showHeaderActionItem(name) {
        this.headerActionItemTypeList.forEach(t => {
            (this.menu[t] || []).forEach(item => {
                item = item || {};

                if (item.name === name) {
                    item.hidden = false;
                }
            });
        });

        let processUi = () => {
            this.$headerActionsContainer.find('li > .action[data-name="'+name+'"]').parent().removeClass('hidden');
            this.$headerActionsContainer.find('a.action[data-name="'+name+'"]').removeClass('hidden');

            this.controlMenuDropdownVisibility();
            this.adjustButtons();
        };

        if (!this.isRendered()) {
            if (this.isBeingRendered()) {
                this.whenRendered().then(() => processUi());
            }

            return;
        }

        processUi();
    }

    /**
     * Whether a menu has any non-hidden dropdown items.
     *
     * @private
     * @returns {boolean}
     */
    hasMenuVisibleDropdownItems() {
        let hasItems = false;

        (this.menu.dropdown || []).forEach(item => {
            if (!item.hidden) {
                hasItems = true;
            }
        });

        return hasItems;
    }

    /**
     * @private
     */
    controlMenuDropdownVisibility() {
        let $group = this.$headerActionsContainer.find('.dropdown-group');

        if (this.hasMenuVisibleDropdownItems()) {
            $group.removeClass('hidden');
            $group.find('> button').removeClass('hidden');

            return;
        }

        $group.addClass('hidden');
        $group.find('> button').addClass('hidden');
    }

    /**
     * @protected
     * @return {module:views/header}
     */
    getHeaderView() {
        return this.getView('header');
    }

    /**
     * @private
     */
    adjustButtons() {
        let $buttons = this.$headerActionsContainer.find('.btn');

        $buttons
            .removeClass('radius-left')
            .removeClass('radius-right');

        let $buttonsVisible = $buttons.filter(':not(.hidden)');

        $buttonsVisible.first().addClass('radius-left');
        $buttonsVisible.last().addClass('radius-right');
    }

    /**
     * Called when a stored view is reused (by the controller).
     *
     * @public
     * @param {Object.<string, *>} params Routing params.
     */
    setupReuse(params) {}
}

export default MainView;
PK]?��views/webhook/fields/user.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/webhook/fields/user', ['views/fields/link'], function (Dep) {

    return Dep.extend({

        selectPrimaryFilterName: 'activeApi',

    });
});
PK]��i�((views/webhook/fields/event.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/webhook/fields/event', ['views/fields/varchar'], function (Dep) {

    return Dep.extend({

        setupOptions: function () {
            var itemList = [];

            var scopeList = this.getMetadata().getScopeObjectList();

            scopeList = scopeList.sort(function (v1, v2) {
                return v1.localeCompare(v2);
            }.bind(this));

            scopeList.forEach(function (scope) {
                itemList.push(scope + '.' + 'create');
                itemList.push(scope + '.' + 'update');
                itemList.push(scope + '.' + 'delete');
            }, this);

            this.params.options = itemList;
        },
    });
});
PK]Ĩ��  views/webhook/record/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/webhook/record/list', ['views/record/list'], function (Dep) {

    return Dep.extend({

        massActionList: ['remove', 'massUpdate', 'export'],

    });
});
PK]��J�g
g
*views/email-account/fields/email-folder.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email-account/fields/email-folder', ['views/fields/link'], function (Dep) {

    return Dep.extend({

        createDisabled: true,
        autocompleteDisabled: true,

        getSelectFilters: function () {
            if (this.getUser().isAdmin()) {
                if (this.model.get('assignedUserId')) {
                    return {
                        assignedUser: {
                            type: 'equals',
                            attribute: 'assignedUserId',
                            value: this.model.get('assignedUserId'),
                            data: {
                                type: 'is',
                                nameValue: this.model.get('assignedUserName'),
                            },
                        }
                    };
                }
            }
        },

        setup: function () {
            Dep.prototype.setup.call(this);

            this.listenTo(this.model, 'change:assignedUserId', (model, e, o) => {
                if (o.ui) {
                    this.model.set({
                        emailFolderId: null,
                        emailFolderName: null,
                    });
                }
            });
        },
    });
});
PK]�}��%views/email-account/fields/folders.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email-account/fields/folders', ['views/fields/array'], function (Dep) {

    return Dep.extend({

        getFoldersUrl: 'EmailAccount/action/getFolders',

        setupOptions: function () {
            this.params.options = ['INBOX'];
        },

        fetchFolders: function () {
            return new Promise(resolve => {
                var data = {
                    host: this.model.get('host'),
                    port: this.model.get('port'),
                    security: this.model.get('security'),
                    username: this.model.get('username'),
                    emailAddress: this.model.get('emailAddress'),
                    userId: this.model.get('assignedUserId'),
                };

                if (this.model.has('password')) {
                    data.password = this.model.get('password');
                }

                if (!this.model.isNew()) {
                    data.id = this.model.id;
                }

                Espo.Ajax.postRequest(this.getFoldersUrl, data)
                    .then(folders => {
                        resolve(folders);
                    })
                    .catch(xhr =>{
                        Espo.Ui.error(this.translate('couldNotConnectToImap', 'messages', 'EmailAccount'));

                        xhr.errorIsHandled = true;

                        resolve(["INBOX"]);
                    });
            });
        },

        actionAddItem: function () {
            Espo.Ui.notify(' ... ');

            this.fetchFolders()
                .then(options => {
                    Espo.Ui.notify(false);

                    this.createView( 'addModal', this.addItemModalView, {options: options})
                        .then(view => {
                            view.render();

                            view.once('add', item =>{
                                this.addValue(item);

                                view.close();
                            });

                            view.once('add-mass', items => {
                                items.forEach(item => {
                                    this.addValue(item);
                                });

                                view.close();
                            });
                        });
                });
        },
    });
});
PK]�V&&'views/email-account/fields/test-send.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email-account/fields/test-send', ['views/outbound-email/fields/test-send'], function (Dep) {

    return Dep.extend({

        checkAvailability: function () {
            if (this.model.get('smtpHost')) {
                this.$el.find('button').removeClass('hidden');
            } else {
                this.$el.find('button').addClass('hidden');
            }
        },

        afterRender: function () {
            this.checkAvailability();

            this.stopListening(this.model, 'change:smtpHost');

            this.listenTo(this.model, 'change:smtpHost', () => {
                this.checkAvailability();
            });
        },

        getSmtpData: function () {
            return {
                'server': this.model.get('smtpHost'),
                'port': this.model.get('smtpPort'),
                'auth': this.model.get('smtpAuth'),
                'security': this.model.get('smtpSecurity'),
                'username': this.model.get('smtpUsername'),
                'password': this.model.get('smtpPassword') || null,
                'authMechanism': this.model.get('smtpAuthMechanism'),
                'fromName': this.getUser().get('name'),
                'fromAddress': this.model.get('emailAddress'),
                'type': 'emailAccount',
                'id': this.model.id,
                'userId': this.model.get('assignedUserId'),
            };
        },
    });
});
PK]r����+views/email-account/fields/email-address.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email-account/fields/email-address', ['views/fields/email-address'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            this.on('change', () => {
                var emailAddress = this.model.get('emailAddress');
                this.model.set('name', emailAddress);
            });

            var userId = this.model.get('assignedUserId');

            if (this.getUser().isAdmin() && userId !== this.getUser().id) {
                Espo.Ajax.getRequest('User/' + userId).then((data) => {
                    var list = [];

                    if (data.emailAddress) {
                        list.push(data.emailAddress);

                        this.params.options = list;

                        if (data.emailAddressData) {
                            data.emailAddressData.forEach(item => {
                                if (item.emailAddress === data.emailAddress) {
                                    return;
                                }

                                list.push(item.emailAddress);
                            });
                        }

                        this.reRender();
                    }
                });
            }
        },

        setupOptions: function () {
            if (this.model.get('assignedUserId') === this.getUser().id) {
                this.params.options = this.getUser().get('userEmailAddressList');
            }
        },

    });
});
PK]�k�==-views/email-account/fields/test-connection.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email-account/fields/test-connection', ['views/fields/base'], function (Dep) {

    return Dep.extend({

        readOnly: true,

        templateContent:
            '<button class="btn btn-default disabled" data-action="testConnection">'+
            '{{translate \'Test Connection\' scope=\'EmailAccount\'}}</button>',

        url: 'EmailAccount/action/testConnection',

        events: {
            'click [data-action="testConnection"]': function () {
                this.test();
            },
        },

        fetch: function () {
            return {};
        },

        checkAvailability: function () {
            if (this.model.get('host')) {
                this.$el.find('button').removeClass('disabled').removeAttr('disabled');
            } else {
                this.$el.find('button').addClass('disabled').attr('disabled', 'disabled');
            }
        },

        afterRender: function () {
            this.checkAvailability();

            this.stopListening(this.model, 'change:host');

            this.listenTo(this.model, 'change:host', () => {
                this.checkAvailability();
            });
        },

        getData: function () {
            return {
                'host': this.model.get('host'),
                'port': this.model.get('port'),
                'security': this.model.get('security'),
                'username': this.model.get('username'),
                'password': this.model.get('password') || null,
                'id': this.model.id,
                emailAddress: this.model.get('emailAddress'),
                userId: this.model.get('assignedUserId'),
            };
        },

        test: function () {
            let data = this.getData();

            let $btn = this.$el.find('button');

            $btn.addClass('disabled');

            Espo.Ui.notify(this.translate('pleaseWait', 'messages'));

            Espo.Ajax.postRequest(this.url, data)
                .then(() => {
                    $btn.removeClass('disabled');

                    Espo.Ui.success(this.translate('connectionIsOk', 'messages', 'EmailAccount'));
                })
                .catch(xhr => {
                    let statusReason = xhr.getResponseHeader('X-Status-Reason') || '';
                    statusReason = statusReason.replace(/ $/, '');
                    statusReason = statusReason.replace(/,$/, '');

                    let msg = this.translate('Error');

                    if (parseInt(xhr.status) !== 200) {
                        msg += ' ' + xhr.status;
                    }

                    if (statusReason) {
                        msg += ': ' + statusReason;
                    }

                    Espo.Ui.error(msg, true);

                    console.error(msg);

                    xhr.errorIsHandled = true;

                    $btn.removeClass('disabled');
                });
        },
    });
});
PK]C�kG#
#
$views/email-account/fields/folder.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email-account/fields/folder', ['views/fields/base'], function (Dep) {

    return Dep.extend({

        editTemplate: 'email-account/fields/folder/edit',

        getFoldersUrl: 'EmailAccount/action/getFolders',

        events: {
            'click [data-action="selectFolder"]': function () {
                Espo.Ui.notify(this.translate('pleaseWait', 'messages'));

                var data = {
                    host: this.model.get('host'),
                    port: this.model.get('port'),
                    security: this.model.get('security'),
                    username: this.model.get('username'),
                    emailAddress: this.model.get('emailAddress'),
                    userId: this.model.get('assignedUserId'),
                };

                if (this.model.has('password')) {
                    data.password = this.model.get('password');
                }

                if (!this.model.isNew()) {
                    data.id = this.model.id;
                }

                Espo.Ajax.postRequest(this.getFoldersUrl, data).then(folders => {
                    this.createView('modal', 'views/email-account/modals/select-folder', {
                        folders: folders
                    }, view => {
                        Espo.Ui.notify(false);

                        view.render();

                        this.listenToOnce(view, 'select', (folder) => {
                            view.close();

                            this.addFolder(folder);
                        });
                    });
                })
                .catch(xhr => {
                    Espo.Ui.error(this.translate('couldNotConnectToImap', 'messages', 'EmailAccount'));

                    xhr.errorIsHandled = true;
                });
            }
        },

        addFolder: function (folder) {
            this.$element.val(folder);
        },
    });
});
PK]�ސ�
�
"views/email-account/record/edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email-account/record/edit', ['views/record/edit', 'views/email-account/record/detail'],
function (Dep, Detail) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            Detail.prototype.setupFieldsBehaviour.call(this);
            Detail.prototype.initSslFieldListening.call(this);
            Detail.prototype.initSmtpFieldsControl.call(this);

            if (this.getUser().isAdmin()) {
                this.setFieldNotReadOnly('assignedUser');
            } else {
                this.setFieldReadOnly('assignedUser');
            }
        },

        modifyDetailLayout: function (layout) {
            Detail.prototype.modifyDetailLayout.call(this, layout);
        },

        setupFieldsBehaviour: function () {
            Detail.prototype.setupFieldsBehaviour.call(this);
        },

        controlStatusField: function () {
            Detail.prototype.controlStatusField.call(this);
        },

        controlSmtpFields: function () {
            Detail.prototype.controlSmtpFields.call(this);
        },

        controlSmtpAuthField: function () {
            Detail.prototype.controlSmtpAuthField.call(this);
        },

        wasFetched: function () {
            Detail.prototype.wasFetched.call(this);
        },
    });
});
PK]g�<���$views/email-account/record/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email-account/record/detail', ['views/record/detail'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            this.setupFieldsBehaviour();
            this.initSslFieldListening();
            this.initSmtpFieldsControl();

            if (this.getUser().isAdmin()) {
                this.setFieldNotReadOnly('assignedUser');
            } else {
                this.setFieldReadOnly('assignedUser');
            }
        },

        modifyDetailLayout: function (layout) {
            layout.filter(panel => panel.tabLabel === '$label:SMTP').forEach(panel => {
                panel.rows.forEach(row => {
                    row.forEach(item => {
                        let labelText = this.translate(item.name, 'fields', 'EmailAccount');

                        if (labelText && labelText.indexOf('SMTP ') === 0) {
                            item.labelText = Espo.Utils.upperCaseFirst(labelText.substring(5));
                        }
                    });
                })
            });
        },

        setupFieldsBehaviour: function () {
            this.controlStatusField();

            this.listenTo(this.model, 'change:status', (model, value, o) => {
                if (o.ui) {
                    this.controlStatusField();
                }
            });

            this.listenTo(this.model, 'change:useImap', (model, value, o) => {
                if (o.ui) {
                    this.controlStatusField();
                }
            });

            if (this.wasFetched()) {
                this.setFieldReadOnly('fetchSince');
            } else {
                this.setFieldNotReadOnly('fetchSince');
            }
        },

        controlStatusField: function () {
            let list = ['username', 'port', 'host', 'monitoredFolders'];

            if (this.model.get('status') === 'Active' && this.model.get('useImap')) {
                list.forEach(item => {
                    this.setFieldRequired(item);
                });

                return;
            }

            list.forEach(item => {
                this.setFieldNotRequired(item);
            });
        },

        wasFetched: function () {
            if (!this.model.isNew()) {
                return !!((this.model.get('fetchData') || {}).lastUID);
            }

            return false;
        },

        initSslFieldListening: function () {
            this.listenTo(this.model, 'change:security', (model, value, o) => {
                if (!o.ui) {
                    return;
                }

                if (value) {
                    this.model.set('port', 993);
                } else {
                    this.model.set('port', 143);
                }
            });

            this.listenTo(this.model, 'change:smtpSecurity', (model, value, o) => {
                if (o.ui) {
                    if (value === 'SSL') {
                        this.model.set('smtpPort', 465);
                    } else if (value === 'TLS') {
                        this.model.set('smtpPort', 587);
                    } else {
                        this.model.set('smtpPort', 25);
                    }
                }
            });
        },

        initSmtpFieldsControl: function () {
            this.controlSmtpFields();

            this.listenTo(this.model, 'change:useSmtp', this.controlSmtpFields, this);
            this.listenTo(this.model, 'change:smtpAuth', this.controlSmtpFields, this);
        },

        controlSmtpFields: function () {
            if (this.model.get('useSmtp')) {
                this.showField('smtpHost');
                this.showField('smtpPort');
                this.showField('smtpAuth');
                this.showField('smtpSecurity');
                this.showField('smtpTestSend');

                this.setFieldRequired('smtpHost');
                this.setFieldRequired('smtpPort');

                this.controlSmtpAuthField();

                return;
            }

            this.hideField('smtpHost');
            this.hideField('smtpPort');
            this.hideField('smtpAuth');
            this.hideField('smtpUsername');
            this.hideField('smtpPassword');
            this.hideField('smtpAuthMechanism');
            this.hideField('smtpSecurity');
            this.hideField('smtpTestSend');

            this.setFieldNotRequired('smtpHost');
            this.setFieldNotRequired('smtpPort');
            this.setFieldNotRequired('smtpUsername');
        },

        controlSmtpAuthField: function () {
            if (this.model.get('smtpAuth')) {
                this.showField('smtpUsername');
                this.showField('smtpPassword');
                this.showField('smtpAuthMechanism');
                this.setFieldRequired('smtpUsername');

                return;
            }

            this.hideField('smtpUsername');
            this.hideField('smtpPassword');
            this.hideField('smtpAuthMechanism');
            this.setFieldNotRequired('smtpUsername');
        },
    });
});
PK]�C���"views/email-account/record/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email-account/record/list', ['views/record/list'], function (Dep) {

    return Dep.extend({

    	quickDetailDisabled: true,
        quickEditDisabled: true,
        checkAllResultDisabled: true,
        massActionList: ['remove', 'massUpdate'],
    });
});
PK]~�+views/email-account/modals/select-folder.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email-account/modals/select-folder', ['views/modal'], function (Dep) {

    return Dep.extend({

        cssName: 'select-folder-modal',

        template: 'email-account/modals/select-folder',

        data: function () {
            return {
                folders: this.options.folders,
            };
        },

        events: {
            'click [data-action="select"]': function (e) {
                var value = $(e.currentTarget).data('value');

                this.trigger('select', value);
            },
        },

        setup: function () {
            this.headerText = this.translate('Select');
        },
    });
});
PK]}�#+	+	views/email-account/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email-account/list', ['views/list'], function (Dep) {

    return Dep.extend({

        keepCurrentRootUrl: true,

        setup: function () {
            Dep.prototype.setup.call(this);

            this.options.params = this.options.params || {};

            var params = this.options.params || {};
            if (params.userId) {
                this.collection.where = [{
                    type: 'equals',
                    field: 'assignedUserId',
                    value: params.userId
                }];
            }
        },

        getCreateAttributes: function () {
            var attributes = {};
            if (this.options.params.userId) {
                attributes.assignedUserId = this.options.params.userId;
                attributes.assignedUserName = this.options.params.userName || this.options.params.userId;
            }
            return attributes;
        },

    });
});
PK]:���.�.views/login.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/login */

import View from 'view';
import Base64 from 'js-base64';
import $ from 'jquery';

class LoginView extends View {

    /** @inheritDoc */
    template = 'login'

    /** @inheritDoc */
    views = {
        footer: {
            fullSelector: 'body > footer',
            view: 'views/site/footer',
        },
    }

    /**
     * @type {string|null}
     * @private
     */
    anotherUser = null

    /** @private */
    isPopoverDestroyed = false

    /**
     * @type {module:handlers/login}
     * @private
     */
    handler = null

    /**
     * @type {boolean}
     * @private
     */
    fallback = false

    /**
     * @type {string|null}
     * @private
     */
    method = null

    /** @inheritDoc */
    events = {
        /** @this LoginView */
        'submit #login-form': function (e) {
            e.preventDefault();

            this.login();
        },
        /** @this LoginView */
        'click #sign-in': function () {
            this.signIn();
        },
        /** @this LoginView */
        'click a[data-action="passwordChangeRequest"]': function () {
            this.showPasswordChangeRequest();
        },
        /** @this LoginView */
        'click a[data-action="showFallback"]': function () {
            this.showFallback();
        },
        /** @this LoginView */
        'keydown': function (e) {
            if (Espo.Utils.getKeyFromKeyEvent(e) === 'Control+Enter') {
                e.preventDefault();

                if (
                    this.handler &&
                    (!this.fallback || !this.$username.val())
                ) {
                    this.signIn();

                    return;
                }

                this.login();
            }
        },
    }

    /** @inheritDoc */
    data() {
        return {
            logoSrc: this.getLogoSrc(),
            showForgotPassword: this.getConfig().get('passwordRecoveryEnabled'),
            anotherUser: this.anotherUser,
            hasSignIn: !!this.handler,
            hasFallback: !!this.handler && this.fallback,
            method: this.method,
            signInText: this.signInText,
            logInText: this.logInText,
        };
    }

    /** @inheritDoc */
    setup() {
        this.anotherUser = this.options.anotherUser || null;

        let loginData = this.getConfig().get('loginData') || {};

        this.fallback = !!loginData.fallback;
        this.method = loginData.method;

        if (loginData.handler) {
            this.wait(
                Espo.loader
                    .requirePromise(loginData.handler)
                    .then(Handler => {
                        this.handler = new Handler(this, loginData.data || {});
                    })
            );

            this.signInText = this.getLanguage().has(this.method, 'signInLabels', 'Global') ?
                this.translate(this.method, 'signInLabels') :
                this.translate('Sign in');
        }

        if (this.getLanguage().has('Log in', 'labels', 'Global')) {
            this.logInText = this.translate('Log in');
        }

        this.logInText = this.getLanguage().has('Log in', 'labels', 'Global') ?
            this.translate('Log in') :
            this.translate('Login');
    }

    /**
     * @private
     * @return {string}
     */
    getLogoSrc() {
        let companyLogoId = this.getConfig().get('companyLogoId');

        if (!companyLogoId) {
            return this.getBasePath() +
                (this.getConfig().get('logoSrc') || 'client/img/logo.svg');
        }

        return this.getBasePath() + '?entryPoint=LogoImage&id=' + companyLogoId;
    }

    /** @inheritDoc */
    afterRender() {
        this.$submit = this.$el.find('#btn-login');
        this.$signIn = this.$el.find('#sign-in');
        this.$username = this.$el.find('#field-userName');
        this.$password = this.$el.find('#field-password');

        if (this.options.prefilledUsername) {
            this.$username.val(this.options.prefilledUsername);
        }

        if (this.handler) {
            this.$username.closest('.cell').addClass('hidden');
            this.$password.closest('.cell').addClass('hidden');
            this.$submit.closest('.cell').addClass('hidden');
        }
    }

    /** @private */
    signIn() {
        this.disableForm();

        this.handler
            .process()
            .then(headers => {
                this.proceed(headers);
            })
            .catch(() => {
                this.undisableForm();
            })
    }

    /** @private */
    login() {
        let authString;
        let userName = this.$username.val();
        let password = this.$password.val();

        let trimmedUserName = userName.trim();

        if (trimmedUserName !== userName) {
            this.$username.val(trimmedUserName);

            userName = trimmedUserName;
        }

        if (userName === '') {
            this.processEmptyUsername();

            return;
        }

        this.disableForm();

        try {
            authString = Base64.encode(userName  + ':' + password);
        }
        catch (e) {
            Espo.Ui.error(this.translate('Error') + ': ' + e.message, true);

            this.undisableForm();

            throw e;
        }

        let headers = {
            'Authorization': 'Basic ' + authString,
            'Espo-Authorization': authString,
        };

        this.proceed(headers, userName, password);
    }

    /**
     * @private
     * @param {Object.<string, string>} headers
     * @param {string} [userName]
     * @param {string} [password]
     */
    proceed(headers, userName, password) {
        headers = Espo.Utils.clone(headers);

        let initialHeaders = Espo.Utils.clone(headers);

        headers['Espo-Authorization-By-Token'] = 'false';
        headers['Espo-Authorization-Create-Token-Secret'] = 'true';

        if (this.anotherUser !== null) {
            headers['X-Another-User'] = this.anotherUser;
        }

        this.notifyLoading();

        Espo.Ajax
            .getRequest('App/user', null, {
                login: true,
                headers: headers,
            })
            .then(data => {
                Espo.Ui.notify(false);

                this.triggerLogin(userName, data);
            })
            .catch(xhr => {
                this.undisableForm();

                if (xhr.status === 401) {
                    let data = xhr.responseJSON || {};
                    let statusReason = xhr.getResponseHeader('X-Status-Reason');

                    if (statusReason === 'second-step-required') {
                        xhr.errorIsHandled = true;
                        this.onSecondStepRequired(initialHeaders, userName, password, data);

                        return;
                    }

                    this.onWrongCredentials();
                }
            });
    }

    /**
     * Trigger login to proceed to the application.
     *
     * @private
     * @param {string|null} userName A username.
     * @param {Object.<string, *>} data Data returned from the `App/user` request.
     */
    triggerLogin(userName, data) {
        if (this.anotherUser) {
            data.anotherUser = this.anotherUser;
        }

        if (!userName) {
            userName = (data.user || {}).userName;
        }

        this.trigger('login', userName, data);
    }

    /** @private */
    processEmptyUsername() {
        this.isPopoverDestroyed = false;

        let $el = this.$username;

        let message = this.getLanguage().translate('userCantBeEmpty', 'messages', 'User');

        $el
            .popover({
                placement: 'bottom',
                container: 'body',
                content: message,
                trigger: 'manual',
            })
            .popover('show');

        let $cell = $el.closest('.form-group');

        $cell.addClass('has-error');

        $el.one('mousedown click', () => {
            $cell.removeClass('has-error');

            if (this.isPopoverDestroyed) {
                return;
            }

            $el.popover('destroy');

            this.isPopoverDestroyed = true;
        });
    }

    /** @private */
    disableForm() {
        this.$submit.addClass('disabled').attr('disabled', 'disabled');
        this.$signIn.addClass('disabled').attr('disabled', 'disabled');
    }

    /** @private */
    undisableForm() {
        this.$submit.removeClass('disabled').removeAttr('disabled');
        this.$signIn.removeClass('disabled').removeAttr('disabled');
    }

    /**
     * @private
     * @param {Object.<string, string>} headers
     * @param {string} userName
     * @param {string} password
     * @param {Object.<string, *>} data
     */
    onSecondStepRequired(headers, userName, password, data) {
        let view = data.view || 'views/login-second-step';

        this.trigger('redirect', view, headers, userName, password, data);
    }

    /** @private */
    onWrongCredentials() {
        let $cell = $('#login .form-group');

        $cell.addClass('has-error');

        this.$el.one('mousedown click', () => {
            $cell.removeClass('has-error');
        });

        let messageKey = this.handler ?
            'failedToLogIn' :
            'wrongUsernamePassword';

        Espo.Ui.error(this.translate(messageKey, 'messages', 'User'));
    }

    /** @private */
    showFallback() {
        this.$el.find('[data-action="showFallback"]').addClass('hidden');

        this.$el.find('.panel-body').addClass('fallback-shown');

        this.$username.closest('.cell').removeClass('hidden');
        this.$password.closest('.cell').removeClass('hidden');
        this.$submit.closest('.cell').removeClass('hidden');
    }

    /** @private */
    notifyLoading() {
        Espo.Ui.notify(' ... ');
    }

    /** @private */
    showPasswordChangeRequest() {
        this.notifyLoading();

        this.createView('passwordChangeRequest', 'views/modals/password-change-request', {
            url: window.location.href,
        }, view => {
            view.render();

            Espo.Ui.notify(false);
        });
    }
}

export default LoginView;
PK]@����	�	%views/extension/record/row-actions.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/extension/record/row-actions', ['views/record/row-actions/default'], function (Dep) {

    return Dep.extend({

        getActionList: function () {
            if (!this.options.acl.edit) {
                return [];
            }

            if (this.model.get('isInstalled')) {
                return [
                    {
                        action: 'uninstall',
                        label: 'Uninstall',
                        data: {
                            id: this.model.id,
                        },
                    },
                ];
            }

            return [
                {
                    action: 'install',
                    label: 'Install',
                    data: {
                        id: this.model.id,
                    },
                },
                {
                    action: 'quickRemove',
                    label: 'Remove',
                    data: {
                        id: this.model.id,
                    },
                },
            ];
        },
    });
});
PK]�d���views/extension/record/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/extension/record/list', ['views/record/list'], function (Dep) {

    return Dep.extend({

        rowActionsView: 'views/extension/record/row-actions',

        checkboxes: false,

    	quickDetailDisabled: true,

        quickEditDisabled: true,

        massActionList: [],
    });
});
PK]�� views/role/record/panels/side.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/role/record/panels/side', ['views/record/panels/side'], function (Dep) {

    return Dep.extend({

        template: 'role/record/panels/side',

    });
});

PK]�Hf���views/role/record/edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/role/record/edit', ['views/record/edit'], function (Dep) {

    return Dep.extend({

        tableView: 'views/role/record/table',

        sideView: false,

        isWide: true,

        stickButtonsContainerAllTheWay: true,

        fetch: function () {
            var data = Dep.prototype.fetch.call(this);

            data['data'] = {};

            var scopeList = this.getView('extra').scopeList;
            var actionList = this.getView('extra').actionList;
            var aclTypeMap = this.getView('extra').aclTypeMap;

            for (var i in scopeList) {
                var scope = scopeList[i];

                if (this.$el.find('select[name="' + scope + '"]').val() === 'not-set') {
                    continue;
                }

                if (this.$el.find('select[name="' + scope + '"]').val() === 'disabled') {
                    data['data'][scope] = false;
                } else {
                    var o = true;

                    if (aclTypeMap[scope] !== 'boolean') {
                        o = {};

                        for (var j in actionList) {
                            var action = actionList[j];
                            o[action] = this.$el.find('select[name="' + scope + '-' + action + '"]').val();
                        }
                    }

                    data['data'][scope] = o;
                }
            }

            data['data'] = this.getView('extra').fetchScopeData();
            data['fieldData'] = this.getView('extra').fetchFieldData();

            return data;
        },

        getDetailLayout: function (callback) {
            var simpleLayout = [
                {
                    label: '',
                    cells: [
                        {
                            name: 'name',
                            type: 'varchar',
                        },
                    ]
                }
            ];
            callback({
                type: 'record',
                layout: this._convertSimplifiedLayout(simpleLayout)
            });
        },

        setup: function () {
            Dep.prototype.setup.call(this);

            this.createView('extra', this.tableView, {
                mode: 'edit',
                selector: '.extra',
                model: this.model,
            }, view => {
                this.listenTo(view, 'change', () => {
                    var data = this.fetch();
                    this.model.set(data);
                });
            });
        },
    });
});
PK]�zE�kkviews/role/record/table.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/role/record/table', ['view'], function (Dep) {

    return Dep.extend({

        template: 'role/table',

        scopeList: null,

        actionList: ['create', 'read', 'edit', 'delete', 'stream'],

        accessList: ['not-set', 'enabled', 'disabled'],

        fieldLevelList: ['yes', 'no'],

        fieldActionList: ['read', 'edit'],

        levelListMap: {
            'recordAllTeamOwnNo': ['all', 'team', 'own', 'no'],
            'recordAllTeamNo': ['all', 'team', 'no'],
            'recordAllOwnNo': ['all', 'own', 'no'],
            'recordAllNo': ['all', 'no'],
            'record': ['all', 'team', 'own', 'no'],
        },

        type: 'acl',

        levelList: ['yes', 'all', 'team', 'own', 'no'],

        booleanLevelList: ['yes', 'no'],

        booleanActionList: ['create'],

        defaultLevels: {
            delete: 'no',
        },

        colors: {
            yes: '#6BC924',
            all: '#6BC924',
            account: '#999900',
            contact: '#999900',
            team: '#999900',
            own: '#CC9900',
            no: '#F23333',
            enabled: '#6BC924',
            disabled: '#F23333',
            'not-set': '#A8A8A8',
        },

        mode: 'detail',

        tableData: null,

        data: function () {
            var data = {};
            data.editMode = this.mode === 'edit';
            data.actionList = this.actionList;
            data.accessList = this.accessList;
            data.fieldActionList = this.fieldActionList;
            data.fieldLevelList = this.fieldLevelList;
            data.colors = this.colors;

            data.tableDataList = this.getTableDataList();
            data.fieldTableDataList = this.fieldTableDataList;

            var hasFieldLevelData = false;

            this.fieldTableDataList.forEach((d) => {
                if (d.list.length) {
                    hasFieldLevelData = true;
                }
            });

            data.hasFieldLevelData = hasFieldLevelData;

            return data;
        },

        events: {
            'click .action[data-action="addField"]': function (e) {
                var scope = $(e.currentTarget).data().scope;

                this.showAddFieldModal(scope);
            },
            'click .action[data-action="removeField"]': function (e) {
                var scope = $(e.currentTarget).data().scope;
                var field = $(e.currentTarget).data().field;

                this.removeField(scope, field);
            },
            'change select[data-type="access"]': function (e) {
                var scope = $(e.currentTarget).attr('name');
                var $dropdowns = this.$el.find('select[data-scope="' + scope + '"]');

                if ($(e.currentTarget).val() === 'enabled') {
                    $dropdowns.removeAttr('disabled');
                    $dropdowns.removeClass('hidden');

                    $dropdowns.each((i, select) => {
                        let $select = $(select);

                        if (this.lowestLevelByDefault) {
                            $select.find('option').last().prop('selected', true);
                        } else {
                            var setFirst = true;
                            var action = $select.data('role-action');
                            var defaultLevel = null;

                            if (action) {
                                defaultLevel = this.defaultLevels[action];
                            }

                            if (defaultLevel) {
                                var $option = $select.find('option[value="'+defaultLevel+'"]');
                                if ($option.length) {
                                    $option.prop('selected', true);
                                    setFirst = false;
                                }
                            }

                            if (setFirst) {
                                $select.find('option').first().prop('selected', true);
                            }
                        }

                        $select.trigger('change');

                        this.controlSelectColor($select);
                    });
                } else {
                    $dropdowns.attr('disabled', 'disabled');
                    $dropdowns.addClass('hidden');
                }

                this.controlSelectColor($(e.currentTarget));
            },
            'change select.scope-action': function (e) {
                this.controlSelectColor($(e.currentTarget));
            },
            'change select.field-action': function (e) {
                this.controlSelectColor($(e.currentTarget));
            },
        },

        getTableDataList: function () {
            var aclData = this.acl.data;
            var aclDataList = [];

            this.scopeList.forEach(scope => {

                var access = 'not-set';

                if (this.final) {
                    access = 'enabled';
                }

                if (scope in aclData) {
                    if (aclData[scope] === false) {
                        access = 'disabled';
                    } else {
                        access = 'enabled';
                    }
                }

                var list = [];
                var type = this.aclTypeMap[scope];

                if (this.aclTypeMap[scope] !== 'boolean') {
                    this.actionList.forEach(action => {
                        var allowedActionList = this.getMetadata().get(['scopes', scope, this.type + 'ActionList']);

                        if (allowedActionList) {
                            if (!~allowedActionList.indexOf(action)) {
                                list.push({
                                    action: action,
                                    levelList: false,
                                    level: null,
                                });

                                return;
                            }
                        }

                        if (action === 'stream') {
                            if (!this.getMetadata().get('scopes.' + scope + '.stream')) {
                                list.push({
                                    action: 'stream',
                                    levelList: false,
                                    level: null,
                                });

                                return;
                            }
                        }

                        var level = 'no';

                        if (~this.booleanActionList.indexOf(action)) {
                            level = 'no';
                        }

                        if (scope in aclData) {
                            if (access === 'enabled') {
                                if (aclData[scope] !== true) {
                                    if (action in aclData[scope]) {
                                        level = aclData[scope][action];
                                    }
                                }
                            } else {
                                level = 'no';
                            }
                        }

                        var levelList =
                            this.getMetadata().get(['scopes', scope, this.type + 'ActionLevelListMap', action]) ||
                            this.getMetadata().get(['scopes', scope, this.type + 'LevelList']) ||
                            this.levelListMap[type] ||
                            [];

                        if (~this.booleanActionList.indexOf(action)) {
                            levelList = this.booleanLevelList;
                        }

                        list.push({
                            level: level,
                            name: scope + '-' + action,
                            action: action,
                            levelList: levelList,
                        });
                    });
                }

                aclDataList.push({
                    list: list,
                    access: access,
                    name: scope,
                    type: type,
                });
            });

            return aclDataList;
        },

        setup: function () {
            this.mode = this.options.mode || 'detail';

            this.final = this.options.final || false;

            this.setupData();

            this.listenTo(this.model, 'change', () => {
                if (this.model.hasChanged('data') || this.model.hasChanged('fieldData')) {
                    this.setupData();
                }
            });

            this.listenTo(this.model, 'sync', () => {
                this.setupData();

                if (this.isRendered()) {
                    this.reRender();
                }
            });

            this.template = 'role/table';

            if (this.mode === 'edit') {
                this.template = 'role/table-edit';
            }

            this.once('remove', () => {
                $(window).off('scroll.scope-' + this.cid);
                $(window).off('resize.scope-' + this.cid);
                $(window).off('scroll.field-' + this.cid);
                $(window).off('resize.field-' + this.cid);
            });
        },

        setupData: function () {
            this.acl = {};

            if (this.options.acl) {
                this.acl.data = this.options.acl.data;
            } else {
                this.acl.data = Espo.Utils.cloneDeep(this.model.get('data') || {});
            }

            if (this.options.acl) {
                this.acl.fieldData = this.options.acl.fieldData;
            } else {
                this.acl.fieldData = Espo.Utils.cloneDeep(this.model.get('fieldData') || {});
            }

            this.setupScopeList();
            this.setupFieldTableDataList();
        },

        setupScopeList: function () {
            this.aclTypeMap = {};
            this.scopeList = [];

            var scopeListAll = Object.keys(this.getMetadata().get('scopes'))
                .sort((v1, v2) => {
                     return this.translate(v1, 'scopeNamesPlural')
                         .localeCompare(this.translate(v2, 'scopeNamesPlural'));
                });

            scopeListAll.forEach(scope => {
                if (this.getMetadata().get('scopes.' + scope + '.disabled')) {
                    return;
                }

                var acl = this.getMetadata().get('scopes.' + scope + '.acl');

                if (acl) {
                    this.scopeList.push(scope);
                    this.aclTypeMap[scope] = acl;

                    if (acl === true) {
                        this.aclTypeMap[scope] = 'record';
                    }
                }
            });
        },

        setupFieldTableDataList: function () {
            this.fieldTableDataList = [];

            this.scopeList.forEach(scope => {
                var d = this.getMetadata().get('scopes.' + scope) || {};

                if (!d.entity) {
                    return;
                }

                if (!(scope in this.acl.fieldData)) {
                    if (this.mode === 'edit') {
                        this.fieldTableDataList.push({
                            name: scope,
                            list: [],
                        });

                        return;
                    }

                    return;
                }

                var scopeData = this.acl.fieldData[scope];
                var fieldList = this.getFieldManager().getEntityTypeFieldList(scope);

                this.getLanguage().sortFieldList(scope, fieldList);

                var fieldDataList = [];

                fieldList.forEach(field => {
                    if (!(field in scopeData)) {
                        return;
                    }

                    var list = [];

                    this.fieldActionList.forEach(action => {
                        list.push({
                            name: action,
                            value: scopeData[field][action] || 'yes',
                        })
                    });

                    if (this.mode === 'detail') {
                        if (!list.length) {
                            return;
                        }
                    }

                    fieldDataList.push({
                        name: field,
                        list: list
                    });
                });

                this.fieldTableDataList.push({
                    name: scope,
                    list: fieldDataList,
                });
            });
        },

        fetchScopeData: function () {
            var data = {};

            var scopeList = this.scopeList;
            var actionList = this.actionList;
            var aclTypeMap = this.aclTypeMap;

            for (var i in scopeList) {
                var scope = scopeList[i];

                if (this.$el.find('select[name="' + scope + '"]').val() === 'not-set') {
                    continue;
                }

                if (this.$el.find('select[name="' + scope + '"]').val() === 'disabled') {
                    data[scope] = false;
                } else {
                    var o = true;

                    if (aclTypeMap[scope] !== 'boolean') {
                        o = {};

                        for (var j in actionList) {
                            var action = actionList[j];

                            o[action] = this.$el.find('select[name="' + scope + '-' + action + '"]').val();
                        }
                    }

                    data[scope] = o;
                }
            }

            return data;
        },

        fetchFieldData: function () {
            var data = {};

            this.fieldTableDataList.forEach(scopeData => {
                var scopeObj = {};
                var scope = scopeData.name;

                scopeData.list.forEach(fieldData => {
                    var field = fieldData.name;
                    var fieldObj = {};

                    this.fieldActionList.forEach(action =>{
                        var $select = this.$el
                            .find('select[data-scope="'+scope+'"][data-field="'+field+'"][data-action="'+action+'"]');

                        if (!$select.length) {
                            return;
                        }

                        fieldObj[action] = $select.val();
                    });

                    scopeObj[field] = fieldObj;
                });

                data[scope] = scopeObj;
            });

            return data;
        },

        afterRender: function () {
            if (this.mode === 'edit') {
                this.scopeList.forEach(scope => {
                    var $read = this.$el.find('select[name="'+scope+'-read"]');

                    $read.on('change', () => {
                        var value = $read.val();

                        this.controlEditSelect(scope, value);
                        this.controlDeleteSelect(scope, value);
                        this.controlStreamSelect(scope, value);
                    });

                    var $edit = this.$el.find('select[name="'+scope+'-edit"]');

                    $edit.on('change', () => {
                        var value = $edit.val();

                        this.controlDeleteSelect(scope, value);
                    });

                    this.controlEditSelect(scope, $read.val(), true);
                    this.controlStreamSelect(scope, $read.val(), true);
                    this.controlDeleteSelect(scope, $edit.val(), true);
                });

                this.fieldTableDataList.forEach(o => {
                    var scope = o.name;

                    o.list.forEach(f => {
                        var field = f.name;

                        var $read = this.$el
                            .find('select[data-scope="'+scope+'"][data-field="'+field+'"][data-action="read"]');

                        $read.on('change', () => {
                            var value = $read.val();

                            this.controlFieldEditSelect(scope, field, value);
                        });

                        this.controlFieldEditSelect(scope, field, $read.val(), true);
                    });
                });

                this.setSelectColors();
            }

            if (this.mode === 'edit' || this.mode === 'detail') {
                this.initStickyHeader('scope');
                this.initStickyHeader('field');
            }
        },

        controlFieldEditSelect: function (scope, field, value, dontChange) {
            var $edit = this.$el.find('select[data-scope="'+scope+'"][data-field="'+field+'"][data-action="edit"]');

            if (!dontChange) {
                if (this.fieldLevelList.indexOf($edit.val()) < this.fieldLevelList.indexOf(value)) {
                    $edit.val(value);
                }
            }

            $edit.find('option').each((i, o) => {
                var $o = $(o);

                if (this.fieldLevelList.indexOf($o.val()) < this.fieldLevelList.indexOf(value)) {
                    $o.attr('disabled', 'disabled');
                } else {
                    $o.removeAttr('disabled');
                }
            });

            this.controlSelectColor($edit);
        },

        controlEditSelect: function (scope, value, dontChange) {
            var $edit = this.$el.find('select[name="'+scope+'-edit"]');

            if (!dontChange) {
                if (this.levelList.indexOf($edit.val()) < this.levelList.indexOf(value)) {
                    $edit.val(value);
                }
            }

            $edit.find('option').each((i, o) => {
                var $o = $(o);

                if (this.levelList.indexOf($o.val()) < this.levelList.indexOf(value)) {
                    $o.attr('disabled', 'disabled');
                } else {
                    $o.removeAttr('disabled');
                }
            });

            this.controlSelectColor($edit);
        },

        controlStreamSelect: function (scope, value, dontChange) {
            var $stream = this.$el.find('select[name="'+scope+'-stream"]');

            if (!dontChange) {
                if (this.levelList.indexOf($stream.val()) < this.levelList.indexOf(value)) {
                    $stream.val(value);
                }
            }

            $stream.find('option').each((i, o) => {
                var $o = $(o);

                if (this.levelList.indexOf($o.val()) < this.levelList.indexOf(value)) {
                    $o.attr('disabled', 'disabled');
                } else {
                    $o.removeAttr('disabled');
                }
            });

            this.controlSelectColor($stream);
        },

        controlDeleteSelect: function (scope, value, dontChange) {
            var $delete = this.$el.find('select[name="'+scope+'-delete"]');

            if (!dontChange) {
                if (this.levelList.indexOf($delete.val()) < this.levelList.indexOf(value)) {
                    $delete.val(value);
                }
            }

            $delete.find('option').each((i, o) => {
                var $o = $(o);

                if (this.levelList.indexOf($o.val()) < this.levelList.indexOf(value)) {
                    $o.attr('disabled', 'disabled');
                } else {
                    $o.removeAttr('disabled');
                }
            });

            this.controlSelectColor($delete);
        },

        showAddFieldModal: function (scope) {
            this.trigger('change');

            var ignoreFieldList = Object.keys(this.acl.fieldData[scope] || {});

            this.createView('addField', 'views/role/modals/add-field', {
                scope: scope,
                ignoreFieldList: ignoreFieldList,
                type: this.type,
            }, (view) => {
                view.render();

                this.listenTo(view, 'add-field', field => {
                    view.close();

                    this.fieldTableDataList.forEach(scopeData =>{
                        if (scopeData.name !== scope) {
                            return;
                        }

                        var found = false;

                        scopeData.list.forEach(d => {
                            if (d.name === field) {
                                found = true;
                            }
                        });

                        if (found) {
                            return;
                        }

                        scopeData.list.unshift({
                            name: field,
                            list: [
                                {
                                    name: 'read',
                                    value: 'yes'
                                },
                                {
                                    name: 'edit',
                                    value: 'yes'
                                }
                            ]
                        });
                    });

                    this.reRender();
                });
            });
        },

        removeField: function (scope, field) {
            this.trigger('change');

            this.fieldTableDataList.forEach(scopeData => {
                if (scopeData.name !== scope) {
                    return;
                }

                var index = -1;

                scopeData.list.forEach((d, i) => {
                    if (d.name === field) {
                        index = i;
                    }
                });

                if (~index) {
                    scopeData.list.splice(index, 1);

                    this.reRender();
                }
            });
        },

        initStickyHeader: function (type) {
            var $sticky = this.$el.find('.sticky-header-' + type);
            var $window = $(window);

            var screenWidthXs = this.getThemeManager().getParam('screenWidthXs');

            var $buttonContainer = $('.detail-button-container');
            var $table = this.$el.find('table.' + type + '-level');

            if (!$table.length) {
                return;
            }

            if (!$buttonContainer.length) {
                return;
            }

            var handle = () => {
                if ($(window.document).width() < screenWidthXs) {
                    $sticky.addClass('hidden');

                    return;
                }

                let stickTopPosition = $buttonContainer.get(0).getBoundingClientRect().top +
                    $buttonContainer.outerHeight();

                let topEdge = $table.position().top;

                topEdge -= $buttonContainer.height();
                topEdge += $table.find('tr > th').height();
                topEdge -= this.getThemeManager().getParam('navbarHeight');

                let bottomEdge = topEdge + $table.outerHeight(true) - $buttonContainer.height();
                let scrollTop = $window.scrollTop();
                let width = $table.width();

                if (scrollTop > topEdge && scrollTop < bottomEdge) {
                    $sticky.css({
                        position: 'fixed',
                        marginTop: stickTopPosition + 'px',
                        top: 0,
                        width: width + 'px',
                        marginLeft: '1px',
                    });

                    $sticky.removeClass('hidden');
                } else {
                    $sticky.addClass('hidden');
                }
            };

            $window.off('scroll.' + type + '-' + this.cid);
            $window.on('scroll.' + type + '-' + this.cid, handle);

            $window.off('resize.' + type + '-' + this.cid);
            $window.on('resize.' + type + '-' + this.cid, handle);
        },

        setSelectColors: function () {
            this.$el.find('select[data-type="access"]').each((i, el) => {
                var $select = $(el);
                this.controlSelectColor($select);
            });

            this.$el.find('select.scope-action').each((i, el) => {
                var $select = $(el);
                this.controlSelectColor($select);
            });

            this.$el.find('select.field-action').each((i, el) => {
                var $select = $(el);
                this.controlSelectColor($select);
            });
        },

        controlSelectColor: function ($select) {
            var level = $select.val();
            var color = this.colors[level] || '';

            if (level === 'not-set') {
                color = '';
            }

            $select.css('color', color);

            $select.children().each((j, el) => {
                var $o = $(el);
                var level = $o.val();

                var color = this.colors[level] || '';

                if (level === 'not-set') {
                    color = '';
                }

                if ($o.attr('disabled')) {
                    color = '';
                }

                $o.css('color', color);
            });
        },
    });
});
PK]�>�N��views/role/record/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/role/record/detail', ['views/record/detail'], function (Dep) {

    return Dep.extend({

        tableView: 'views/role/record/table',

        sideView: false,
        isWide: true,
        editModeDisabled: true,
        stickButtonsContainerAllTheWay: true,

        setup: function () {
            Dep.prototype.setup.call(this);

            this.createView('extra', this.tableView, {
                selector: '.extra',
                model: this.model
            });
        },
    });
});
PK]�r��~~views/role/record/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/role/record/list', ['views/record/list'], function (Dep) {

    return Dep.extend({

    	quickDetailDisabled: true,

        quickEditDisabled: true,

        massActionList: ['remove', 'export'],

        checkAllResultDisabled: true

    });
});
PK]xvd�� views/role/record/detail-side.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/role/record/detail-side', ['views/record/detail-side'], function (Dep) {

    return Dep.extend({

        panelList: [
            {
                name: 'default',
                label: false,
                view: 'views/role/record/panels/side',
            }
        ],
    });
});
PK]�{��//views/role/modals/add-field.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/role/modals/add-field', ['views/modal'], function (Dep) {

    return Dep.extend({

        template: 'role/modals/add-field',

        events: {
            'click a[data-action="addField"]': function (e) {
                this.trigger('add-field', $(e.currentTarget).data().name);
            }
        },

        data: function () {
            var dataList = [];

            this.fieldList.forEach((field, i) => {
                if (i % 4 === 0) {
                    dataList.push([]);
                }

                dataList[dataList.length -1].push(field);
            });

            return {
                dataList: dataList,
                scope: this.scope
            };
        },

        setup: function () {
            this.headerText = this.translate('Add Field');

            var scope = this.scope = this.options.scope;
            var fields = this.getMetadata().get('entityDefs.' + scope + '.fields') || {};
            var fieldList = [];

            Object.keys(fields).forEach(field => {
                var d = fields[field];

                if (field in this.options.ignoreFieldList) {
                    return;
                }

                if (d.disabled) {
                    return;
                }

                if (
                    this.getMetadata()
                        .get(['app', this.options.type, 'mandatory', 'scopeFieldLevel', this.scope, field]) !== null
                ) {
                    return;
                }

                fieldList.push(field);
            });

            this.fieldList = this.getLanguage().sortFieldList(scope, fieldList);
        },
    });
});
PK]��5'��views/role/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/role/list', ['views/list'], function (Dep) {

    return Dep.extend({

        searchPanel: false,
    });
});
PK]�6�i;;views/user/fields/password.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/user/fields/password', ['views/fields/password'], function (Dep) {

    return Dep.extend({

        validations: ['required', 'strength', 'confirm'],

        setup: function () {
            Dep.prototype.setup.call(this);
        },

        init: function () {
            var tooltipItemList = [];

            this.strengthParams = this.options.strengthParams || {
                passwordStrengthLength: this.getConfig().get('passwordStrengthLength'),
                passwordStrengthLetterCount: this.getConfig().get('passwordStrengthLetterCount'),
                passwordStrengthNumberCount: this.getConfig().get('passwordStrengthNumberCount'),
                passwordStrengthBothCases: this.getConfig().get('passwordStrengthBothCases'),
            };

            var minLength = this.strengthParams.passwordStrengthLength;
            if (minLength) {
                tooltipItemList.push(
                    '* ' + this.translate('passwordStrengthLength', 'messages', 'User').replace('{length}', minLength.toString())
                );
            }

            var requiredLetterCount = this.strengthParams.passwordStrengthLetterCount;
            if (requiredLetterCount) {
                tooltipItemList.push(
                    '* ' + this.translate('passwordStrengthLetterCount', 'messages', 'User').replace('{count}', requiredLetterCount.toString())
                );
            }

            var requiredNumberCount = this.strengthParams.passwordStrengthNumberCount;
            if (requiredNumberCount) {
                tooltipItemList.push(
                    '* ' + this.translate('passwordStrengthNumberCount', 'messages', 'User').replace('{count}', requiredNumberCount.toString())
                );
            }

            var bothCases = this.strengthParams.passwordStrengthBothCases;
            if (bothCases) {
                tooltipItemList.push(
                    '* ' + this.translate('passwordStrengthBothCases', 'messages', 'User')
                );
            }

            if (tooltipItemList.length) {
                this.tooltip = true;
                this.tooltipText = this.translate('Requirements', 'labels', 'User') + ':\n' + tooltipItemList.join('\n');
            }

            Dep.prototype.init.call(this);
        },

        validateStrength: function () {
            if (!this.model.get(this.name)) return;

            var password = this.model.get(this.name);

            var minLength = this.strengthParams.passwordStrengthLength;
            if (minLength) {
                if (password.length < minLength) {
                    var msg = this.translate('passwordStrengthLength', 'messages', 'User').replace('{length}', minLength.toString());
                    this.showValidationMessage(msg);
                    return true;;
                }
            }

            var requiredLetterCount = this.strengthParams.passwordStrengthLetterCount;
            if (requiredLetterCount) {
                var letterCount = 0;
                password.split('').forEach(function (c) {
                    if (c.toLowerCase() !== c.toUpperCase()) letterCount++;
                }, this);

                if (letterCount < requiredLetterCount) {
                    var msg = this.translate('passwordStrengthLetterCount', 'messages', 'User').replace('{count}', requiredLetterCount.toString());
                    this.showValidationMessage(msg);
                    return true;;
                }
            }

            var requiredNumberCount = this.strengthParams.passwordStrengthNumberCount;
            if (requiredNumberCount) {
                var numberCount = 0;
                password.split('').forEach(function (c) {
                    if (c >= '0' && c <= '9') numberCount++;
                }, this);

                if (numberCount < requiredNumberCount) {
                    var msg = this.translate('passwordStrengthNumberCount', 'messages', 'User').replace('{count}', requiredNumberCount.toString());
                    this.showValidationMessage(msg);
                    return true;;
                }
            }

            var bothCases = this.strengthParams.passwordStrengthBothCases;
            if (bothCases) {
                var ucCount = 0;
                password.split('').forEach(function (c) {
                    if (c.toLowerCase() !== c.toUpperCase() && c === c.toUpperCase()) ucCount++;
                }, this);
                var lcCount = 0;
                password.split('').forEach(function (c) {
                    if (c.toLowerCase() !== c.toUpperCase() && c === c.toLowerCase()) lcCount++;
                }, this);

                if (!ucCount || !lcCount) {
                    var msg = this.translate('passwordStrengthBothCases', 'messages', 'User');
                    this.showValidationMessage(msg);
                    return true;
                }
            }
        },

    });
});
PK]�W��&views/user/fields/generate-password.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/user/fields/generate-password', ['views/fields/base'], function (Dep) {

    return Dep.extend({

        templateContent: '<button type="button" class="btn btn-default" data-action="generatePassword">' +
            '{{translate \'Generate\' scope=\'User\'}}</button>',

        events: {
            'click [data-action="generatePassword"]': function () {
                this.actionGeneratePassword();
            },
        },

        setup: function () {
            Dep.prototype.setup.call(this);

            this.listenTo(this.model, 'change:password', (model, value, o) => {
                if (o.isGenerated) {
                    return;
                }

                this.model.set({
                    passwordPreview: '',
                });
            });

            this.strengthParams = this.options.strengthParams || {};

            this.passwordStrengthLength = this.strengthParams.passwordStrengthLength ||
                this.getConfig().get('passwordStrengthLength');

            this.passwordStrengthLetterCount = this.strengthParams.passwordStrengthLetterCount ||
                this.getConfig().get('passwordStrengthLetterCount');

            this.passwordStrengthNumberCount = this.strengthParams.passwordStrengthNumberCount ||
                this.getConfig().get('passwordStrengthNumberCount');

            this.passwordGenerateLength = this.strengthParams.passwordGenerateLength ||
                this.getConfig().get('passwordGenerateLength');

            this.passwordGenerateLetterCount = this.strengthParams.passwordGenerateLetterCount ||
                this.getConfig().get('passwordGenerateLetterCount');

            this.passwordGenerateNumberCount = this.strengthParams.passwordGenerateNumberCount ||
                this.getConfig().get('passwordGenerateNumberCount');
        },

        fetch: function () {
            return {};
        },

        actionGeneratePassword: function () {
            var length = this.passwordStrengthLength;
            var letterCount = this.passwordStrengthLetterCount;
            var numberCount = this.passwordStrengthNumberCount;

            var generateLength = this.passwordGenerateLength || 10;
            var generateLetterCount = this.passwordGenerateLetterCount || 4;
            var generateNumberCount = this.passwordGenerateNumberCount || 2;

            length = (typeof length === 'undefined') ? generateLength : length;
            letterCount = (typeof letterCount === 'undefined') ? generateLetterCount : letterCount;
            numberCount = (typeof numberCount === 'undefined') ? generateNumberCount : numberCount;

            if (length < generateLength) length = generateLength;
            if (letterCount < generateLetterCount) letterCount = generateLetterCount;
            if (numberCount < generateNumberCount) numberCount = generateNumberCount;

            var password = this.generatePassword(length, letterCount, numberCount, true);

            this.model.set({
                password: password,
                passwordConfirm: password,
                passwordPreview: password,
            }, {isGenerated: true});
        },

        generatePassword: function (length, letters, numbers, bothCases) {
            var chars = [
                'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
                '0123456789',
                'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
                'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
                'abcdefghijklmnopqrstuvwxyz',
            ];

            var upperCase = 0;
            var lowerCase = 0;

            if (bothCases) {
                upperCase = 1;
                lowerCase = 1;

                if (letters >= 2) {
                    letters = letters - 2;
                } else {
                    letters = 0;
                }
            }

            var either = length - (letters + numbers + upperCase + lowerCase);

            if (either < 0) {
                either = 0;
            }

            var setList = [letters, numbers, either, upperCase, lowerCase];

            var shuffle = function (array) {
                var currentIndex = array.length, temporaryValue, randomIndex;

                while (0 !== currentIndex) {
                    randomIndex = Math.floor(Math.random() * currentIndex);
                    currentIndex -= 1;
                    temporaryValue = array[currentIndex];
                    array[currentIndex] = array[randomIndex];
                    array[randomIndex] = temporaryValue;
                }

                return array;
            };

            var array = setList.map(
                function (len, i) {
                    return Array(len).fill(chars[i]).map(
                        function (x) {
                            return x[Math.floor(Math.random() * x.length)];
                        }
                    ).join('');
                }
            ).concat();

            return shuffle(array).join('');
        },

    });
});
PK]/ƍ�IIviews/user/fields/teams.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/user/fields/teams', ['views/fields/link-multiple-with-role'], function (Dep) {

    return Dep.extend({

        forceRoles: true,

        setup: function () {
            Dep.prototype.setup.call(this);

            this.roleListMap = {};

            this.loadRoleList(() => {
                if (this.isEditMode()) {
                    if (this.isRendered() || this.isBeingRendered()) {
                        this.reRender();
                    }
                }
            });

            this.listenTo(this.model, 'change:teamsIds', () => {
                let toLoad = false;

                this.ids.forEach(id => {
                    if (!(id in this.roleListMap)) {
                        toLoad = true;
                    }
                });

                if (toLoad) {
                    this.loadRoleList(() => {
                        this.reRender();
                    });
                }
            });
        },

        loadRoleList: function (callback, context) {
            if (!this.getAcl().checkScope('Team', 'read')) {
                return;
            }

            let ids = this.ids || [];

            if (ids.length === 0) {
                return;
            }

            this.getCollectionFactory().create('Team', teams => {
                teams.maxSize = 50;
                teams.where = [
                    {
                        type: 'in',
                        field: 'id',
                        value: ids,
                    }
                ];

                this.listenToOnce(teams, 'sync', () => {
                    teams.models.forEach(model => {
                        this.roleListMap[model.id] = model.get('positionList') || [];
                    });

                    callback.call(context);
                });

                teams.fetch();
            });
        },

        getDetailLinkHtml: function (id, name) {
            name = name || this.nameHash[id];

            let role = (this.columns[id] || {})[this.columnName] || '';

            let $el = $('<div>')
                .append(
                    $('<a>')
                        .attr('href', '#' + this.foreignScope + '/view/' + id)
                        .attr('data-id', id)
                        .text(name)
                );

            if (role) {
                role = this.getHelper().escapeString(role);

                $el.append(
                    $('<span>').text(' '),
                    $('<span>').addClass('text-muted chevron-right'),
                    $('<span>').text(' '),
                    $('<span>').addClass('text-muted').text(role)
                )
            }

            return $el.get(0).outerHTML;
        },

        getJQSelect: function (id, roleValue) {
            /** @var {string[]} */
            let roleList = Espo.Utils.clone(this.roleListMap[id] || []);

            if (!roleList.length && !roleValue) {
                return null;
            }

            roleList.unshift('');

            if (roleValue && roleList.indexOf(roleValue) === -1) {
                roleList.push(roleValue);
            }

            let $role = $('<select>')
                .addClass('role form-control input-sm pull-right')
                .attr('data-id', id);

            roleList.forEach(role => {
                let $option = $('<option>')
                    .val(role)
                    .text(role);

                if (role === (roleValue || '')) {
                    $option.attr('selected', 'selected');
                }

                $role.append($option);
            });

            return $role;
        },
    });
});
PK]v�j�views/user/fields/name.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/user/fields/name', ['views/fields/person-name'], function (Dep) {

    return Dep.extend({

        listTemplate: 'user/fields/name/list-link',

        listLinkTemplate: 'user/fields/name/list-link',

        data: function () {
            return _.extend({
                avatar: this.getAvatarHtml(),
                frontScope: this.model.isPortal() ? 'PortalUser': 'User',
                isOwn: this.model.id === this.getUser().id,
            }, Dep.prototype.data.call(this));
        },

        getAvatarHtml: function () {
            return this.getHelper().getAvatarHtml(this.model.id, 'small', 16, 'avatar-link');
        },
    });
});
PK]%��+>
>
views/user/fields/contact.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/user/fields/contact', ['views/fields/link'], function (Dep) {

    return Dep.extend({

        select: function (model) {
            Dep.prototype.select.call(this, model);

            var attributes = {};

            if (model.get('accountId')) {
                var names = {};

                names[model.get('accountId')] = model.get('accountName');
                attributes.accountsIds = [model.get('accountId')];
                attributes.accountsNames = names;
            }

            attributes.firstName = model.get('firstName');
            attributes.lastName = model.get('lastName');
            attributes.salutationName = model.get('salutationName');

            attributes.emailAddress = model.get('emailAddress');
            attributes.emailAddressData = model.get('emailAddressData');

            attributes.phoneNumber = model.get('phoneNumber');
            attributes.phoneNumberData = model.get('phoneNumberData');

            if (this.model.isNew() && !this.model.get('userName') && attributes.emailAddress) {
                attributes.userName = attributes.emailAddress;
            }

            this.model.set(attributes);
        },
    });
});
PK]sv�XX1views/user/fields/auto-follow-entity-type-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/preferences/fields/auto-follow-entity-type-list', ['views/fields/multi-enum'], function (Dep) {

    return Dep.extend({

        setup: function () {
            this.params.options = Object.keys(this.getMetadata().get('scopes'))
                .filter(scope => {
                    return this.getMetadata().get('scopes.' + scope + '.entity') &&
                        this.getMetadata().get('scopes.' + scope + '.stream');
                })
                .sort((v1, v2) => {
                    return this.translate(v1, 'scopeNamesPlural')
                        .localeCompare(this.translate(v2, 'scopeNamesPlural'));
                });

            Dep.prototype.setup.call(this);
        },
    });
});
PK]1B^  views/user/fields/user-name.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/user/fields/user-name', ['views/fields/varchar'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            this.validations.push('userName');
        },

        afterRender: function () {
            Dep.prototype.afterRender.call(this);

            let userNameRegularExpression = this.getUserNameRegularExpression();

            if (this.isEditMode()) {
                this.$element.on('change', () => {
                    let value = this.$element.val();
                    let re = new RegExp(userNameRegularExpression, 'gi');

                    value = value
                        .replace(re, '')
                        .replace(/[\s]/g, '_')
                        .toLowerCase();

                    this.$element.val(value);
                    this.trigger('change');
                });
            }
        },

        getUserNameRegularExpression: function () {
            return this.getConfig().get('userNameRegularExpression') || '[^a-z0-9\-@_\.\s]';
        },

        validateUserName: function () {
            let value = this.model.get(this.name);

            if (!value) {
                return;
            }

            let userNameRegularExpression = this.getUserNameRegularExpression();

            let re = new RegExp(userNameRegularExpression, 'gi');

            if (!re.test(value)) {
                return;
            }

            let msg = this.translate('fieldInvalid', 'messages').replace('{field}', this.getLabelText());

            this.showValidationMessage(msg);

            return true;
        },
    });
});
PK]<Ɛ	views/user/fields/avatar.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/user/fields/avatar', ['views/fields/image'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            this.on('after:inline-save', () => {
                this.suspendCache = true;

                this.reRender();
            });
        },

        handleUploadingFile: function (file) {
            return new Promise((resolve, reject) => {
                let fileReader = new FileReader();

                fileReader.onload = (e) => {
                    this.createView('crop', 'views/modals/image-crop', {contents: e.target.result})
                        .then(view => {
                            view.render();

                            let cropped = false;

                            this.listenToOnce(view, 'crop', (dataUrl) => {
                                cropped = true;

                                setTimeout(() => {
                                    fetch(dataUrl)
                                        .then(result => result.blob())
                                        .then(blob => {
                                            resolve(
                                                new File([blob], 'avatar.jpg', {type: 'image/jpeg'})
                                            );
                                        });
                                }, 10);
                            });

                            this.listenToOnce(view, 'remove', () => {
                                if (!cropped) {
                                    setTimeout(() => this.render(), 10);

                                    reject();
                                }

                                this.clearView('crop');
                            });
                        });
                };

                fileReader.readAsDataURL(file);
            });
        },

        getValueForDisplay: function () {
            if (!this.isReadMode()) {
                return '';
            }

            let id = this.model.get(this.idName);
            let userId = this.model.id;

            let t = this.cacheTimestamp = this.cacheTimestamp || Date.now();

            if (this.suspendCache) {
                t = Date.now();
            }

            let src = this.getBasePath() +
                '?entryPoint=avatar&size=' + this.previewSize + '&id=' + userId +
                '&t=' + t + '&attachmentId=' + (id || 'false');

            let $img = $('<img>')
                .attr('src', src)
                .css({
                    maxWidth: (this.imageSizes[this.previewSize] || {})[0],
                    maxHeight: (this.imageSizes[this.previewSize] || {})[1],
                });

            if (!this.isDetailMode()) {
                if (this.getCache()) {
                    t = this.getCache().get('app', 'timestamp');
                }

                let src = this.getBasePath() + '?entryPoint=avatar&size=' +
                    this.previewSize + '&id=' + userId + '&t=' + t;

                $img
                    .attr('width', '16')
                    .attr('src', src)
                    .css('maxWidth', '16px');
            }

            if (!id) {
                return $img
                    .get(0)
                    .outerHTML;
            }

            return $('<a>')
                .attr('data-id', id)
                .attr('data-action', 'showImagePreview')
                .attr('href', this.getBasePath() + '?entryPoint=image&id=' + id)
                .append($img)
                .get(0)
                .outerHTML;
        },
    });
});
PK]�<

views/user/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/user/detail', ['views/detail'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            if (this.getUser().isPortal()) {
                this.rootLinkDisabled = true;
            }

            if (this.model.id === this.getUser().id || this.getUser().isAdmin()) {

                if (this.model.isRegular() || this.model.isAdmin() || this.model.isPortal()) {
                    this.addMenuItem('dropdown', {
                        name: 'preferences',
                        label: 'Preferences',
                        style: 'default',
                        action: "preferences",
                        link: '#Preferences/edit/' + this.getUser().id
                    });
                }

                if (this.model.isRegular() || this.model.isAdmin()) {
                    if (
                        (this.getAcl().check('EmailAccountScope') && this.model.id === this.getUser().id) ||
                        this.getUser().isAdmin()
                    ) {
                        this.addMenuItem('dropdown', {
                            name: 'emailAccounts',
                            label: "Email Accounts",
                            style: 'default',
                            action: "emailAccounts",
                            link: '#EmailAccount/list/userId=' +
                                this.model.id + '&userName=' + encodeURIComponent(this.model.get('name'))
                        });
                    }

                    if (this.model.id === this.getUser().id && this.getAcl().checkScope('ExternalAccount')) {
                        this.menu.buttons.push({
                            name: 'externalAccounts',
                            label: 'External Accounts',
                            style: 'default',
                            action: "externalAccounts",
                            link: '#ExternalAccount'
                        });
                    }
                }
            }

            if (this.getAcl().checkScope('Calendar') && (this.model.isRegular() || this.model.isAdmin())) {
                var showActivities = this.getAcl().checkUserPermission(this.model);

                if (!showActivities) {
                    if (this.getAcl().get('userPermission') === 'team') {
                        if (!this.model.has('teamsIds')) {
                            this.listenToOnce(this.model, 'sync', function () {
                                if (this.getAcl().checkUserPermission(this.model)) {
                                    this.showHeaderActionItem('calendar');
                                }
                            }, this);
                        }
                    }
                }

                this.menu.buttons.push({
                    name: 'calendar',
                    iconHtml: '<span class="far fa-calendar-alt"></span>',
                    text: this.translate('Calendar', 'scopeNames'),
                    style: 'default',
                    link: '#Calendar/show/userId=' +
                        this.model.id + '&userName=' + encodeURIComponent(this.model.get('name')),
                    hidden: !showActivities
                });
            }
        },

        actionPreferences: function () {
            this.getRouter().navigate('#Preferences/edit/' + this.model.id, {trigger: true});
        },

        actionEmailAccounts: function () {
            this.getRouter()
                .navigate(
                    '#EmailAccount/list/userId=' + this.model.id +
                    '&userName=' + encodeURIComponent(this.model.get('name')),
                    {trigger: true}
                );
        },

        actionExternalAccounts: function () {
            this.getRouter().navigate('#ExternalAccount', {trigger: true});
        },

    });
});
PK]�:��	�	"views/user/record/detail-bottom.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import DetailBottomRecordView from 'views/record/detail-bottom';

class UserDetailBottomRecordView extends  DetailBottomRecordView {

    setupPanels() {
       super.setupPanels();

        let streamAllowed = this.getAcl().checkUserPermission(this.model);

        if (
            !streamAllowed &&
            this.getAcl().getPermissionLevel('userPermission') === 'team' &&
            !this.model.has('teamsIds')
        ) {
            this.listenToOnce(this.model, 'sync', () => {
                if (this.getAcl().checkUserPermission(this.model)) {
                    this.onPanelsReady(() => {
                        this.showPanel('stream', 'acl');
                    });
                }
            });
        }

        this.panelList.push({
            "name": "stream",
            "label": "Stream",
            "view": "views/user/record/panels/stream",
            "sticked": true,
            "hidden": !streamAllowed,
        });

        if (!streamAllowed) {
            this.recordHelper.setPanelStateParam('stream', 'hiddenAclLocked', true);
        }
    }
}

export default UserDetailBottomRecordView;
PK][�M��views/user/record/edit-quick.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/user/record/edit-quick', ['views/record/edit-small', 'views/user/record/detail'], function (Dep, Detail) {

    return Dep.extend({

        sideView: 'views/user/record/edit-side',

        setup: function () {
            Dep.prototype.setup.call(this);
            Detail.prototype.setupNonAdminFieldsAccess.call(this);
            Detail.prototype.setupFieldAppearance.call(this);
        },

        controlFieldAppearance: function () {
            Detail.prototype.controlFieldAppearance.call(this);
        },
    });
});
PK]@Z�  "views/user/record/panels/stream.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/user/record/panels/stream', ['views/stream/panel'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            let assignmentPermission = this.getAcl().checkPermission('message', this.model);

            if (this.model.id === this.getUser().id) {
                this.placeholderText = this.translate('writeMessageToSelf', 'messages');
            } else {
                this.placeholderText = this.translate('writeMessageToUser', 'messages')
                    .replace('{user}', this.model.get('name'));
            }

            if (!assignmentPermission) {
                this.postDisabled = true;

                if (this.getAcl().getPermissionLevel('message') === 'team') {
                    if (!this.model.has('teamsIds')) {
                        this.listenToOnce(this.model, 'sync', () => {
                            assignmentPermission = this.getAcl().checkUserPermission(this.model);

                            if (assignmentPermission) {
                                this.postDisabled = false;
                                this.$el.find('.post-container').removeClass('hidden');
                            }
                        });
                    }
                }
            }
        },

        prepareNoteForPost: function (model) {
            var userIdList = [this.model.id];
            var userNames = {};

            userNames[userIdList] = this.model.get('name');

            model.set('usersIds', userIdList);
            model.set('usersNames', userNames);
            model.set('targetType', 'users');
        },
    });
});
PK]�{��OO(views/user/record/panels/default-side.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/user/record/panels/default-side', ['views/record/panels/default-side'], function (Dep) {

    return Dep.extend({

        complexCreatedDisabled: true,

        complexModifiedDisabled: true,

    });
});
PK]�:'��	�	(views/user/record/row-actions/default.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/user/record/row-actions/default', ['views/record/row-actions/default'], function (Dep) {

    return Dep.extend({

        getActionList: function () {
            var scope = 'User';

            if (this.model.isPortal()) {
                scope = 'PortalUser';
            } else if (this.model.isApi()) {
                scope = 'ApiUser';
            }

            var list = [{
                action: 'quickView',
                label: 'View',
                data: {
                    id: this.model.id,
                    scope: scope
                },
                link: '#' + scope + '/view/' + this.model.id
            }];

            if (this.options.acl.edit) {
                list.push({
                    action: 'quickEdit',
                    label: 'Edit',
                    data: {
                        id: this.model.id,
                        scope: scope
                    },
                    link: '#' + scope + '/edit/' + this.model.id
                });
            }

            return list;
        },
    });
});
PK]a��F�	�	7views/user/record/row-actions/relationship-followers.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/user/record/row-actions/relationship-followers', ['views/record/row-actions/relationship'], function (Dep) {

    return Dep.extend({

        getActionList: function () {
            var list = [{
                action: 'quickView',
                label: 'View',
                data: {
                    id: this.model.id
                },
                link: '#' + this.model.entityType + '/view/' + this.model.id
            }];

            if (
                this.getUser().isAdmin() ||
                this.getAcl().get('followerManagementPermission') !== 'no' ||
                this.model.isPortal() && this.getAcl().get('portalPermission') === 'yes' ||
                this.model.id === this.getUser().id
            ) {
                list.push({
                    action: 'unlinkRelated',
                    label: 'Unlink',
                    data: {
                        id: this.model.id
                    }
                });
            }

            return list;
        }
    });
});
PK]
M�q�+�+views/user/record/edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import EditRecordView from 'views/record/edit';
import UserDetailRecordView from 'views/user/record/detail';

class UserEditRecordView extends EditRecordView {

    sideView = 'views/user/record/edit-side'

    /**
     * @name model
     * @type module:models/user
     * @memberOf UserEditRecordView#
     */

    setup() {
        super.setup();

        this.setupNonAdminFieldsAccess();

        if (this.model.id === this.getUser().id) {
            this.listenTo(this.model, 'after:save', () => {
                this.getUser().set(this.model.getClonedAttributes());
            });
        }

        this.hideField('sendAccessInfo');

        this.passwordInfoMessage = this.getPasswordSendingMessage();

        if (!this.passwordInfoMessage) {
            this.hideField('passwordInfo');
        }

        let passwordChanged = false;

        this.listenToOnce(this.model, 'change:password', () => {
            passwordChanged = true;

            if (this.model.isNew()) {
                this.controlSendAccessInfoFieldForNew();

                return;
            }

            this.controlSendAccessInfoField();
        });

        this.listenTo(this.model, 'change', (model) => {
            if (!this.model.isNew() && !passwordChanged) {
                return;
            }

            if (
                !model.hasChanged('emailAddress') &&
                !model.hasChanged('portalsIds')&&
                !model.hasChanged('password')
            ) {
                return;
            }

            if (this.model.isNew()) {
                this.controlSendAccessInfoFieldForNew();

                return;
            }

            this.controlSendAccessInfoField();
        });

        UserDetailRecordView.prototype.setupFieldAppearance.call(this);

        this.hideField('passwordPreview');

        this.listenTo(this.model, 'change:passwordPreview', (model, value) => {
            value = value || '';

            if (value.length) {
                this.showField('passwordPreview');
            } else {
                this.hideField('passwordPreview');
            }
        });


        this.listenTo(this.model, 'after:save', () => {
            this.model.unset('password', {silent: true});
            this.model.unset('passwordConfirm', {silent: true});
        });
    }

    controlSendAccessInfoField() {
        if (this.isPasswordSendable() && this.model.get('password')) {
            this.showField('sendAccessInfo');

            return;
        }

        this.hideField('sendAccessInfo');

        this.model.set('sendAccessInfo', false);
    }

    controlSendAccessInfoFieldForNew() {
        let skipSettingTrue = this.recordHelper.getFieldStateParam('sendAccessInfo', 'hidden') === false;

        if (this.isPasswordSendable()) {
            this.showField('sendAccessInfo');

            if (!skipSettingTrue) {
                this.model.set('sendAccessInfo', true);
            }

            return;
        }

        this.hideField('sendAccessInfo');

        this.model.set('sendAccessInfo', false);
    }

    // noinspection SpellCheckingInspection
    isPasswordSendable() {
        if (this.model.isPortal()) {
            if (!(this.model.get('portalsIds') || []).length) {
                return false;
            }
        }

        if (!this.model.get('emailAddress')) {
            return false;
        }

        return true;
    }


    setupNonAdminFieldsAccess() {
        UserDetailRecordView.prototype.setupNonAdminFieldsAccess.call(this);
    }

    // noinspection JSUnusedGlobalSymbols
    controlFieldAppearance() {
        UserDetailRecordView.prototype.controlFieldAppearance.call(this);
    }

    getGridLayout(callback) {
        this.getHelper().layoutManager
            .get(this.model.entityType, this.options.layoutName || this.layoutName, simpleLayout => {
                let layout = Espo.Utils.cloneDeep(simpleLayout);

                layout.push({
                    "label": "Teams and Access Control",
                    "name": "accessControl",
                    "rows": [
                        [{"name": "type"}, {"name": "isActive"}],
                        [{"name": "teams"}, {"name": "defaultTeam"}],
                        [{"name": "roles"}, false]
                    ]
                });

                layout.push({
                    "label": "Portal",
                    "name": "portal",
                    "rows": [
                        [{"name": "portals"}, {"name": "contact"}],
                        [{"name": "portalRoles"}, {"name": "accounts"}]
                    ]
                });

                if (this.getUser().isAdmin() && this.model.isPortal()) {
                    layout.push({
                        "label": "Misc",
                        "name": "portalMisc",
                        "rows": [
                            [{"name": "dashboardTemplate"}, false]
                        ]
                    });
                }

                if (this.model.isAdmin() || this.model.isRegular()) {
                    layout.push({
                        "label": "Misc",
                        "name": "misc",
                        "rows": [
                            [{"name": "workingTimeCalendar"}, {"name": "layoutSet"}]
                        ]
                    });
                }

                if (
                    this.type === this.TYPE_EDIT &&
                    this.getUser().isAdmin() &&
                    !this.model.isApi()
                ) {
                    layout.push({
                        label: 'Password',
                        rows: [
                            [
                                {
                                    name: 'password',
                                    type: 'password',
                                    params: {
                                        required: false,
                                        readyToChange: true,
                                    },
                                    view: 'views/user/fields/password',
                                },
                                {
                                    name: 'generatePassword',
                                    view: 'views/user/fields/generate-password',
                                    customLabel: '',
                                },
                            ],
                            [
                                {
                                    name: 'passwordConfirm',
                                    type: 'password',
                                    params: {
                                        required: false,
                                        readyToChange: true
                                    }
                                },
                                {
                                    name: 'passwordPreview',
                                    view: 'views/fields/base',
                                    params: {
                                        readOnly: true
                                    },
                                },
                            ],
                            [
                                {
                                    name: 'sendAccessInfo'
                                },
                                {
                                    name: 'passwordInfo',
                                    type: 'text',
                                    customLabel: '',
                                    customCode: this.passwordInfoMessage,
                                },
                            ]
                        ]
                    });
                }

                if (this.getUser().isAdmin() && this.model.isApi()) {
                    layout.push({
                        "name": "auth",
                        "rows": [
                            [{"name": "authMethod"}, false]
                        ]
                    });
                }

                let gridLayout = {
                    type: 'record',
                    layout: this.convertDetailLayout(layout),
                };

                callback(gridLayout);
            });
    }

    getPasswordSendingMessage() {
        if (this.getConfig().get('outboundEmailFromAddress')) {
            return '';
        }

        let msg = this.translate('setupSmtpBefore', 'messages', 'User')
            .replace('{url}', '#Admin/outboundEmails');

        msg = this.getHelper().transformMarkdownInlineText(msg);

        return msg;
    }

    fetch() {
        let data = super.fetch();

        if (!this.isNew) {
            if (
                'password' in data &&
                (data['password'] === '' || data['password'] == null)
            ) {
                delete data['password'];
                delete data['passwordConfirm'];

                this.model.unset('password');
                this.model.unset('passwordConfirm');
            }
        }

        return data;
    }

    exit(after) {
        if (after === 'create' || after === 'save') {
            this.model.unset('sendAccessInfo', {silent: true});
        }

        super.exit(after);
    }

    // noinspection JSUnusedGlobalSymbols
    errorHandlerUserNameExists() {
        Espo.Ui.error(this.translate('userNameExists', 'messages', 'User'))
    }
}

export default UserEditRecordView;
PK]��V��&views/user/record/detail-quick-side.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/user/record/detail-quick-side',
['views/record/detail-side', 'views/user/record/detail-side'], function (Dep, UserDetailSide) {

    return Dep.extend({

        setupPanels: function () {
            UserDetailSide.prototype.setupPanels.call(this);
        },
    });
});
PK]()/��:�:views/user/record/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import DetailRecordView from 'views/record/detail';

class UserDetailRecordView extends DetailRecordView {

    sideView = 'views/user/record/detail-side'
    bottomView = 'views/user/record/detail-bottom'

    editModeDisabled = true

    /**
     * @name model
     * @type module:models/user
     * @memberOf UserDetailRecordView#
     */

    setup() {
        super.setup();

        this.setupNonAdminFieldsAccess();

        if (this.getUser().isAdmin() && !this.model.isPortal()) {
            this.addButton({
                name: 'access',
                label: 'Access',
                style: 'default',
                onClick: () => this.actionAccess(),
            });
        }

        let isPortalUser = this.model.isPortal() ||
            this.model.id === this.getUser().id && this.getUser().isPortal();

        if (
            (this.model.id === this.getUser().id || this.getUser().isAdmin()) &&
            this.getConfig().get('auth2FA') &&
            (
                (this.model.isRegular() || this.model.isAdmin()) ||
                isPortalUser && this.getConfig().get('auth2FAInPortal')
            )
        ) {
            this.addButton({
                name: 'viewSecurity',
                label: 'Security',
            });
        }

        if (
            this.model.id === this.getUser().id &&
            !this.model.isApi() &&
            (this.getUser().isAdmin() || !this.getHelper().getAppParam('passwordChangeForNonAdminDisabled'))
        ) {
            this.addDropdownItem({
                name: 'changePassword',
                label: 'Change Password',
                style: 'default'
            });
        }

        if (
            this.getUser().isAdmin() &&
            (
                this.model.isRegular() ||
                this.model.isAdmin() ||
                this.model.isPortal()
            ) &&
            !this.model.isSuperAdmin()
        ) {
            this.addDropdownItem({
                name: 'sendPasswordChangeLink',
                label: 'Send Password Change Link',
                action: 'sendPasswordChangeLink',
                hidden: !this.model.get('emailAddress'),
            });

            this.addDropdownItem({
                name: 'generateNewPassword',
                label: 'Generate New Password',
                action: 'generateNewPassword',
                hidden: !this.model.get('emailAddress'),
            });

            if (!this.model.get('emailAddress')) {
                this.listenTo(this.model, 'sync', () => {
                    if (this.model.get('emailAddress')) {
                        this.showActionItem('generateNewPassword');
                        this.showActionItem('sendPasswordChangeLink');
                    } else {
                        this.hideActionItem('generateNewPassword');
                        this.hideActionItem('sendPasswordChangeLink');
                    }
                });
            }
        }

        if (this.model.isPortal() || this.model.isApi()) {
            this.hideActionItem('duplicate');
        }

        if (this.model.id === this.getUser().id) {
            this.listenTo(this.model, 'after:save', () => {
                this.getUser().set(this.model.getClonedAttributes());
            });
        }

        if (
            this.getUser().isAdmin() &&
            this.model.isRegular() &&
            !this.getConfig().get('authAnotherUserDisabled')
        ) {
            this.addDropdownItem({
                label: 'Log in',
                name: 'login',
                action: 'login',
            });
        }

        this.setupFieldAppearance();
    }

    setupActionItems() {
        super.setupActionItems();

        if (this.model.isApi() && this.getUser().isAdmin()) {
            this.addDropdownItem({
                'label': 'Generate New API Key',
                'name': 'generateNewApiKey'
            });
        }
    }

    setupNonAdminFieldsAccess() {
        if (this.getUser().isAdmin()) {
            return;
        }

        let nonAdminReadOnlyFieldList = [
            'userName',
            'isActive',
            'teams',
            'roles',
            'password',
            'portals',
            'portalRoles',
            'contact',
            'accounts',
            'type',
            'emailAddress',
        ];

        nonAdminReadOnlyFieldList = nonAdminReadOnlyFieldList.filter(item => {
            if (!this.model.hasField(item)) {
                return true;
            }

            let aclDefs = /** @type Object.<string, *>|null */
                this.getMetadata().get(['entityAcl', 'User', 'fields', item]);

            if (!aclDefs) {
                return true;
            }

            if (aclDefs.nonAdminReadOnly) {
                return true;
            }

            return false;
        });

        nonAdminReadOnlyFieldList.forEach((field) => {
            this.setFieldReadOnly(field, true);
        });

        if (!this.getAcl().checkScope('Team')) {
            this.setFieldReadOnly('defaultTeam', true);
        }
    }

    setupFieldAppearance() {
        this.controlFieldAppearance();

        this.listenTo(this.model, 'change', () => {
            this.controlFieldAppearance();
        });
    }

    controlFieldAppearance() {
        if (this.model.get('type') === 'portal') {
            this.hideField('roles');
            this.hideField('teams');
            this.hideField('defaultTeam');
            this.showField('portals');
            this.showField('portalRoles');
            this.showField('contact');
            this.showField('accounts');
            this.showPanel('portal');
            this.hideField('title');
        } else {
            this.showField('roles');
            this.showField('teams');
            this.showField('defaultTeam');
            this.hideField('portals');
            this.hideField('portalRoles');
            this.hideField('contact');
            this.hideField('accounts');
            this.hidePanel('portal');

            if (this.model.get('type') === 'api') {
                this.hideField('title');
                this.hideField('emailAddress');
                this.hideField('phoneNumber');
                this.hideField('name');
                this.hideField('gender');

                if (this.model.get('authMethod') === 'Hmac') {
                    this.showField('secretKey');
                } else {
                    this.hideField('secretKey');
                }

            } else {
                this.showField('title');
            }
        }

        if (this.model.id === this.getUser().id) {
            this.setFieldReadOnly('type');
        } else {
            if (this.model.get('type') === 'admin' || this.model.get('type') === 'regular') {
                this.setFieldNotReadOnly('type');
                this.setFieldOptionList('type', ['regular', 'admin']);
            } else {
                this.setFieldReadOnly('type');
            }
        }

        if (
            !this.getConfig().get('auth2FA')
            ||
            !(this.model.isRegular() || this.model.isAdmin())
        ) {
            this.hideField('auth2FA');
        }
    }

    // noinspection JSUnusedGlobalSymbols
    actionChangePassword() {
        Espo.Ui.notify(' ... ');

        this.createView('changePassword', 'views/modals/change-password', {userId: this.model.id}, view => {
            view.render();
            Espo.Ui.notify(false);

            this.listenToOnce(view, 'changed', () => {
                setTimeout(() => {
                    this.getBaseController().logout();
                }, 2000);
            });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionPreferences() {
        this.getRouter().navigate('#Preferences/edit/' + this.model.id, {trigger: true});
    }

    // noinspection JSUnusedGlobalSymbols
    actionEmailAccounts() {
        this.getRouter().navigate('#EmailAccount/list/userId=' + this.model.id, {trigger: true});
    }

    // noinspection JSUnusedGlobalSymbols
    actionExternalAccounts() {
        this.getRouter().navigate('#ExternalAccount', {trigger: true});
    }

    // noinspection JSUnusedGlobalSymbols
    actionAccess() {
        Espo.Ui.notify(' ... ');

        Espo.Ajax.getRequest(`User/${this.model.id}/acl`).then(aclData => {
            this.createView('access', 'views/user/modals/access', {
                aclData: aclData,
                model: this.model,
            }, view => {
                Espo.Ui.notify(false);

                view.render();
            });
        });
    }

    getGridLayout(callback) {
        this.getHelper().layoutManager
            .get(this.model.entityType, this.options.layoutName || this.layoutName, (simpleLayout) => {

            let layout = Espo.Utils.cloneDeep(simpleLayout);

            if (!this.getUser().isPortal()) {
                layout.push({
                    "label": "Teams and Access Control",
                    "name": "accessControl",
                    "rows": [
                        [{"name":"type"}, {"name":"isActive"}],
                        [{"name":"teams"}, {"name":"defaultTeam"}],
                        [{"name":"roles"}, false],
                    ]
                });

                if (this.model.isPortal()) {
                    layout.push({
                        "label": "Portal",
                        "name": "portal",
                        "rows": [
                            [{"name":"portals"}, {"name":"contact"}],
                            [{"name":"portalRoles"}, {"name":"accounts"}],
                        ]
                    });

                    if (this.getUser().isAdmin()) {
                        layout.push({
                            "label": "Misc",
                            "name": "portalMisc",
                            "rows": [
                                [{"name":"dashboardTemplate"}, false],
                            ],
                        });
                    }
                }

                if (this.model.isAdmin() || this.model.isRegular()) {
                    layout.push({
                        "label": "Misc",
                        "name": "misc",
                        "rows": [
                            [{"name": "workingTimeCalendar"}, {"name": "layoutSet"}],
                        ]
                    });
                }
            }

            if (this.getUser().isAdmin() && this.model.isApi()) {
                layout.push({
                    "name": "auth",
                    "rows": [
                        [{"name":"authMethod"}, false],
                        [{"name":"apiKey"}, {"name":"secretKey"}],
                    ]
                });
            }

            let gridLayout = {
                type: 'record',
                layout: this.convertDetailLayout(layout),
            };

            callback(gridLayout);
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionGenerateNewApiKey() {
        this.confirm(this.translate('confirmation', 'messages'), () => {
            Espo.Ajax
                .postRequest('UserSecurity/apiKey/generate', {id: this.model.id})
                .then((data) => {
                    this.model.set(data);
                });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionViewSecurity() {
        this.createView('dialog', 'views/user/modals/security', {
            userModel: this.model,
        }, view => {
            view.render();
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionSendPasswordChangeLink() {
        this.confirm({
            message: this.translate('sendPasswordChangeLinkConfirmation', 'messages', 'User'),
            confirmText: this.translate('Send', 'labels', 'Email'),
        })
        .then(() => {
            Espo.Ui.notify(this.translate('pleaseWait', 'messages'));

            Espo.Ajax
                .postRequest('UserSecurity/password/recovery', {id: this.model.id})
                .then(() => {
                    Espo.Ui.success(this.translate('Done'));
                });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionGenerateNewPassword() {
        this.confirm(
            this.translate('generateAndSendNewPassword', 'messages', 'User')
        ).then(() => {
            Espo.Ui.notify(this.translate('pleaseWait', 'messages'));

            Espo.Ajax
                .postRequest('UserSecurity/password/generate', {id: this.model.id})
                .then(() => {
                    Espo.Ui.success(this.translate('Done'));
                });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionLogin() {
        let anotherUser = this.model.get('userName');
        let username = this.getUser().get('userName');

        this.createView('dialog', 'views/user/modals/login-as', {
                model: this.model,
                anotherUser: anotherUser,
                username: username,
            })
            .then(view => view.render());
    }
}

export default UserDetailRecordView;
PK]sq���!views/user/record/detail-quick.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/user/record/detail-quick',
['views/record/detail-small', 'views/user/record/detail'], function (Dep, Detail) {

    return Dep.extend({

        sideView: 'views/user/record/detail-quick-side',

        bottomView: null,

        editModeEnabled: false,

        setup: function () {
            Dep.prototype.setup.call(this);
            Detail.prototype.setupNonAdminFieldsAccess.call(this);
            Detail.prototype.setupFieldAppearance.call(this);
        },

        controlFieldAppearance: function () {
            Detail.prototype.controlFieldAppearance.call(this);
        },
    });
});
PK]�vr���views/user/record/edit-side.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/user/record/edit-side', ['views/record/edit-side'], function (Dep) {

    return Dep.extend({

    });
});

PK]�jkj

views/user/record/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/user/record/list', ['views/record/list'], function (Dep) {

    return Dep.extend({

        quickEditDisabled: true,

        rowActionsView: 'views/user/record/row-actions/default',

        massActionList: ['remove', 'massUpdate', 'export'],

        checkAllResultMassActionList: ['massUpdate', 'export'],

        setupMassActionItems: function () {
            Dep.prototype.setupMassActionItems.call(this);

            if (this.scope === 'ApiUser') {
                this.removeMassAction('massUpdate');
                this.removeMassAction('export');

                this.layoutName = 'listApi';
            }

            if (this.scope === 'PortalUser') {
                this.layoutName = 'listPortal';
            }

            if (!this.getUser().isAdmin()) {
                this.removeMassAction('massUpdate');
                this.removeMassAction('export');
            }
        },

        getModelScope: function (id) {
            var model = this.collection.get(id);

            if (model.isPortal()) {
                return 'PortalUser';
            }

            return this.scope;
        },
    });
});
PK]Q�ȑ� views/user/record/detail-side.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/user/record/detail-side', ['views/record/detail-side'], function (Dep) {

    return Dep.extend({

        setupPanels: function () {
            Dep.prototype.setupPanels.call(this);

            if (this.model.isApi() || this.model.isSystem()) {
                this.hidePanel('activities', true);
                this.hidePanel('history', true);
                this.hidePanel('tasks', true);
                this.hidePanel('stream', true);

                return;
            }

            var showActivities = this.getAcl().checkUserPermission(this.model);

            if (!showActivities) {
                if (this.getAcl().get('userPermission') === 'team') {
                    if (!this.model.has('teamsIds')) {
                        this.listenToOnce(this.model, 'sync', function () {
                            if (this.getAcl().checkUserPermission(this.model)) {
                                this.onPanelsReady(function () {
                                    this.showPanel('activities', 'acl');
                                    this.showPanel('history', 'acl');
                                    if (!this.model.isPortal()) {
                                        this.showPanel('tasks', 'acl');
                                    }
                                });
                            }
                        }, this);
                    }
                }
            }

            if (!showActivities) {
                this.hidePanel('activities', false, 'acl');
                this.hidePanel('history', false, 'acl');
                this.hidePanel('tasks', false, 'acl');
            }

            if (this.model.isPortal()) {
                this.hidePanel('tasks', true);
            }
        },

    });
});
PK]V�k�
�
views/user/modals/password.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/user/modals/password', ['views/modal', 'model'], function (Dep, Model) {

    return Dep.extend({

        templateContent: '<div class="record no-side-margin">{{{record}}}</div>',

        className: 'dialog dialog-record',

        shortcutKeys: {
            'Control+Enter': 'apply',
        },

        setup: function () {
            this.buttonList = [
                {
                    name: 'apply',
                    label: 'Apply',
                    style: 'danger',
                },
                {
                    name: 'cancel',
                    label: 'Cancel',
                },
            ];

            this.headerHtml = '&nbsp';

            this.userModel = this.options.userModel;

            var model = this.model = new Model();
            model.name = 'UserSecurity';

            model.setDefs({
                fields: {
                    'password': {
                        type: 'password',
                        required: true,
                    },
                }
            });

            this.createView('record', 'views/record/edit-for-modal', {
                scope: 'None',
                selector: '.record',
                model: this.model,
                detailLayout: [
                    {
                        rows: [
                            [
                                {
                                    name: 'password',
                                    labelText: this.translate('yourPassword', 'fields', 'User'),
                                    params: {
                                        readyToChange: true,
                                    }
                                },
                                false
                            ]
                        ]
                    }
                ],
            });
        },

        actionApply: function () {
            var data = this.getView('record').processFetch();
            if (!data) return;

            this.trigger('proceed', data);
        },

    });
});
PK]�|5� � views/user/modals/security.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ModalView from 'views/modal';
import Model from 'model';

class UserSecurityModalView extends ModalView {

    templateContent = '<div class="record no-side-margin">{{{record}}}</div>'

    className = 'dialog dialog-record'

    shortcutKeys = {
        'Control+Enter': 'apply',
    }

    setup() {
        this.buttonList = [
            {
                name: 'apply',
                label: 'Apply',
                hidden: true,
                style: 'danger',
                onClick: () => this.apply(),
            },
            {
                name: 'cancel',
                label: 'Close',
            }
        ];

        this.dropdownItemList = [
            {
                name: 'reset',
                text: this.translate('Reset 2FA'),
                hidden: true,
                onClick: () => this.reset(),
            },
        ];

        this.userModel = this.options.userModel;

        this.$header = $('<span>').append(
            $('<span>').text(this.translate('Security')),
            ' <span class="chevron-right"></span> ',
            $('<span>').text(this.userModel.get('userName'))
        );

        const model = this.model = new Model();

        model.name = 'UserSecurity';
        model.id = this.userModel.id;
        model.url = 'UserSecurity/' + this.userModel.id;

        let auth2FAMethodList = this.getConfig().get('auth2FAMethodList') || [];

        model.setDefs({
            fields: {
                'auth2FA': {
                    type: 'bool',
                    labelText: this.translate('auth2FAEnable', 'fields', 'User'),
                },
                'auth2FAMethod': {
                    type: 'enum',
                    options: auth2FAMethodList,
                    translation: 'Settings.options.auth2FAMethodList',
                },
            }
        });

        this.wait(
            model.fetch().then(() => {
                this.initialAttributes = Espo.Utils.cloneDeep(model.attributes);

                if (model.get('auth2FA')) {
                    this.showActionItem('reset');
                }

                this.createView('record', 'views/record/edit-for-modal', {
                    scope: 'None',
                    selector: '.record',
                    model: this.model,
                    detailLayout: [
                        {
                            rows: [
                                [
                                    {
                                        name: 'auth2FA',
                                        labelText: this.translate('auth2FAEnable', 'fields', 'User'),
                                    },
                                    {
                                        name: 'auth2FAMethod',
                                        labelText: this.translate('auth2FAMethod', 'fields', 'User'),
                                    }
                                ],
                            ]
                        }
                    ],
                }, (view) => {
                    this.controlFieldsVisibility(view);

                    this.listenTo(this.model, 'change:auth2FA', () => {
                        this.controlFieldsVisibility(view);
                    });
                });
            })
        );

        this.listenTo(this.model, 'change', () => {
            if (this.initialAttributes) {
                this.isChanged() ?
                    this.showActionItem('apply') :
                    this.hideActionItem('apply');
            }
        });
    }

    controlFieldsVisibility(view) {
        if (this.model.get('auth2FA')) {
            view.showField('auth2FAMethod');
            view.setFieldRequired('auth2FAMethod');
        }
        else {
            view.hideField('auth2FAMethod');
            view.setFieldNotRequired('auth2FAMethod');
        }
    }

    isChanged() {
        return this.initialAttributes.auth2FA !== this.model.get('auth2FA') ||
            this.initialAttributes.auth2FAMethod !== this.model.get('auth2FAMethod')
    }

    reset() {
        this.confirm(this.translate('security2FaResetConfirmation', 'messages', 'User'), () => {
            this.apply(true);
        });
    }

    /**
     * @return {module:views/record/edit}
     */
    getRecordView() {
        return this.getView('record');
    }

    apply(reset) {
        let data = this.getRecordView().processFetch();

        if (!data) {
            return;
        }

        this.hideActionItem('apply');

        new Promise(resolve => {
            this.createView('dialog', 'views/user/modals/password', {}, passwordView => {
                passwordView.render();

                this.listenToOnce(passwordView, 'cancel', () => this.showActionItem('apply'));

                this.listenToOnce(passwordView, 'proceed', (data) => {
                    this.model.set('password', data.password);

                    passwordView.close();

                    resolve();
                });
            });
        }).then(() => this.processApply(reset));
    }

    processApply(reset) {
        if (this.model.get('auth2FA')) {
            let auth2FAMethod = this.model.get('auth2FAMethod');

            const view = this.getMetadata().get(['app', 'authentication2FAMethods', auth2FAMethod, 'userApplyView']);

            if (view) {
                Espo.Ui.notify(' ... ');

                this.createView('dialog', view, {
                    model: this.model,
                    reset: reset,
                }, view => {
                    Espo.Ui.notify(false);

                    view.render();

                    this.listenToOnce(view, 'cancel', () => {
                        this.close();
                    });

                    this.listenToOnce(view, 'apply', () => {
                        view.close();

                        this.processSave();
                    });

                    this.listenToOnce(view, 'done', () => {
                        Espo.Ui.success(this.translate('Done'));
                        this.trigger('done');

                        view.close();
                        this.close();
                    });
                });

                return ;
            }

            if (reset) {
                this.model.set('auth2FA', false);
            }

            this.processSave();

            return;
        }

        this.processSave();
    }

    processSave() {
        this.hideActionItem('apply');

        this.model
            .save()
            .then(() => {
                this.close();

                Espo.Ui.success(this.translate('Done'));
            })
            .catch(() => this.showActionItem('apply'));
    }
}

export default UserSecurityModalView;
PK]v��H55 views/user/modals/mass-update.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/user/modals/mass-update', ['views/modals/mass-update'], function (Dep) {

    return Dep.extend({

        setup: function () {

            if (this.options.scope === 'ApiUser') {
                this.layoutName = 'massUpdateApi';
            } else if (this.options.scope === 'PortalUser') {
                this.layoutName = 'massUpdatePortal';
            }

            Dep.prototype.setup.call(this);
        },
    });
});
PK]�		views/user/modals/login-as.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/user/modals/login-as', ['views/modal'], function (Dep) {

    return Dep.extend({

        backdrop: true,

        templateContent: `
            <div class="well">
                {{translate 'loginAs' category='messages' scope='User'}}
            </div>
            <a href="{{viewObject.url}}" class="text-large">{{translate 'Login Link' scope='User'}}</a>
        `,

        setup: function () {
            this.$header = $('<span>')
                .append(
                    $('<span>').text(this.model.get('name')),
                    ' ',
                    $('<span>').addClass('chevron-right'),
                    ' ',
                    $('<span>').text(this.translate('Login')),
                );

            this.url = `?entryPoint=loginAs` +
                `&anotherUser=${this.options.anotherUser}&username=${this.options.username}`;
        },
    });
});
PK],"�|��views/user/modals/access.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/user/modals/access', ['views/modal'], function (Dep) {

    return Dep.extend({

        cssName: 'user-access',

        multiple: false,

        template: 'user/modals/access',

        backdrop: true,

        data: function () {
            return {
                valuePermissionDataList: this.getValuePermissionList(),
                levelListTranslation: this.getLanguage().get('Role', 'options', 'levelList') || {}
            };
        },

        getValuePermissionList: function () {
            var list = this.getMetadata().get(['app', 'acl', 'valuePermissionList'], []);
            var dataList = [];
            list.forEach(function (item) {
                var o = {};
                o.name = item;
                o.value = this.options.aclData[item];
                dataList.push(o);
            }, this);
            return dataList;
        },

        setup: function () {
            this.buttonList = [
                {
                    name: 'cancel',
                    label: 'Cancel'
                }
            ];

            var fieldTable = Espo.Utils.cloneDeep(this.options.aclData.fieldTable || {});

            for (var scope in fieldTable) {
                var scopeData = fieldTable[scope] || {};

                for (var field in scopeData) {
                    if (
                        this.getMetadata()
                            .get(['app', 'acl', 'mandatory', 'scopeFieldLevel', scope, field]) !== null
                    ) {
                        delete scopeData[field];
                    }

                    if (
                        scopeData[field] &&
                        this.getMetadata().get(['entityDefs', scope, 'fields', field, 'readOnly'])
                    ) {
                        if (scopeData[field].edit === 'no' && scopeData[field].read === 'yes') {
                            delete scopeData[field];
                        }
                    }
                }
            }

            this.createView('table', 'views/role/record/table', {
                acl: {
                    data: this.options.aclData.table,
                    fieldData: fieldTable,
                },
                final: true
            });

            this.headerText = this.translate('Access');
        },
    });
});
PK]�n�views/user/modals/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/user/modals/detail', ['views/modals/detail'], function (Dep) {

    return Dep.extend({

        editDisabled: true,

        getScope: function () {
            if (this.model.isPortal()) {
                return 'PortalUser';
            }

            return 'User';
        },
    });
});
PK]��v�		%views/user/modals/select-followers.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/user/modals/select-followers', ['views/modals/select-records'], function (Dep) {

    return Dep.extend({

        setup: function () {
            this.filterList = ['active'];

            if (this.getAcl().getPermissionLevel('portalPermission')) {
                this.filterList.push('activePortal');
            }

            Dep.prototype.setup.call(this);
        },
    });
});
PK]d?��OOviews/user/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/user/list', ['views/list'], function (Dep) {

    return Dep.extend({

        storeViewAfterUpdate: false,

        setup: function () {
            Dep.prototype.setup.call(this);
        },
    });
});
PK]nzR;xx%views/user/password-change-request.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/user/password-change-request', ['view', 'model'], function (Dep, Model) {

    return Dep.extend({

        template: 'user/password-change-request',

        data: function () {
            return {
                requestId: this.options.requestId,
                notFound: this.options.notFound,
                notFoundMessage: this.notFoundMessage,
            };
        },

        events: {
            'click #btn-submit': function () {
                this.submit();
            },
        },

        setup: function () {
            let model = this.model = new Model();
            model.entityType = model.name = 'User';

            this.createView('password', 'views/user/fields/password', {
                model: model,
                mode: 'edit',
                selector: '.field[data-name="password"]',
                defs: {
                    name: 'password',
                    params: {
                        required: true,
                        maxLength: 255,
                    },
                },
                strengthParams: this.options.strengthParams,
            });

            this.createView('passwordConfirm', 'views/fields/password', {
                model: model,
                mode: 'edit',
                selector: '.field[data-name="passwordConfirm"]',
                defs: {
                    name: 'passwordConfirm',
                    params: {
                        required: true,
                        maxLength: 255,
                    },
                },
            });

            this.createView('generatePassword', 'views/user/fields/generate-password', {
                model: model,
                mode: 'detail',
                readOnly: true,
                selector: '.field[data-name="generatePassword"]',
                defs: {
                    name: 'generatePassword',
                },
                strengthParams: this.options.strengthParams,
            });

            this.createView('passwordPreview', 'views/fields/base', {
                model: model,
                mode: 'detail',
                readOnly: true,
                selector: '.field[data-name="passwordPreview"]',
                defs: {
                    name: 'passwordPreview',
                },
            });

            this.model.on('change:passwordPreview', () => this.reRender());

            let url = this.baseUrl = window.location.href.split('?')[0];

            this.notFoundMessage = this.translate('passwordChangeRequestNotFound', 'messages', 'User')
                .replace('{url}', url);
        },

        submit: function () {
            this.getView('password').fetchToModel();
            this.getView('passwordConfirm').fetchToModel();

            var notValid = this.getView('password').validate() ||
                this.getView('passwordConfirm').validate();

            var password = this.model.get('password');

            if (notValid) {
                return;
            }

            let $submit = this.$el.find('.btn-submit');

            $submit.addClass('disabled');

            Espo.Ajax
                .postRequest('User/changePasswordByRequest', {
                    requestId: this.options.requestId,
                    password: password,
                })
                .then(data => {
                    this.$el.find('.password-change').remove();

                    var url = data.url || this.baseUrl;

                    var msg = this.translate('passwordChangedByRequest', 'messages', 'User') +
                        ' <a href="' + url + '">' + this.translate('Login', 'labels', 'User') + '</a>.';

                    this.$el.find('.msg-box')
                        .removeClass('hidden')
                        .html('<span class="text-success">' + msg + '</span>');
                })
                .catch(() =>
                    $submit.removeClass('disabled')
                );
        },

    });
});
PK]>�\��X�Xviews/list-related.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module module:views/list-related */

import MainView from 'views/main';
import SearchManager from 'search-manager';

/**
 * A list-related view.
 */
class ListRelatedView extends MainView {

    /** @inheritDoc */
    template = 'list'

    /** @inheritDoc */
    name = 'ListRelated'

    /**
     * A header view name.
     *
     * @type {string}
     */
    headerView = 'views/header'

    /**
     * A search view name.
     *
     * @type {string}
     */
    searchView = 'views/record/search'

    /**
     * A record/list view name.
     *
     * @type {string}
     */
    recordView = 'views/record/list'

    /**
     * Has a search panel.
     *
     * @type {boolean}
     */
    searchPanel = true

    /**
     * @type {module:search-manager}
     */
    searchManager = null

    /**
     * @inheritDoc
     */
    optionsToPass = []

    /**
     * Use a current URL as a root URL when open a record. To be able to return to the same URL.
     */
    keepCurrentRootUrl = false

    /**
     * A view mode.
     *
     * @type {string}
     */
    viewMode = ''

    /**
     * An available view mode list.
     *
     * @type {string[]|null}
     */
    viewModeList = null

    /**
     * A default view mode.
     *
     * @type {string}
     */
    defaultViewMode = 'list'

    /**
     * @const
     */
    MODE_LIST = 'list'

    /**
     * @private
     */
    rowActionsView = 'views/record/row-actions/relationship'

    /**
     * A create button.
     *
     * @protected
     */
    createButton = true

    /**
     * @protected
     */
    unlinkDisabled = false

    /**
     * @protected
     */
    filtersDisabled = false

    /**
     * @inheritDoc
     */
    shortcutKeys = {
        /** @this ListRelatedView */
        'Control+Space': function (e) {
            this.handleShortcutKeyCtrlSpace(e);
        },
        /** @this ListRelatedView */
        'Control+Slash': function (e) {
            this.handleShortcutKeyCtrlSlash(e);
        },
        /** @this ListRelatedView */
        'Control+Comma': function (e) {
            this.handleShortcutKeyCtrlComma(e);
        },
        /** @this ListRelatedView */
        'Control+Period': function (e) {
            this.handleShortcutKeyCtrlPeriod(e);
        },
    }

    /**
     * @inheritDoc
     */
    setup() {
        this.link = this.options.link;

        if (!this.link) {
            console.error(`Link not passed.`);
            throw new Error();
        }

        if (!this.model) {
            console.error(`Model not passed.`);
            throw new Error();
        }

        if (!this.collection) {
            console.error(`Collection not passed.`);
            throw new Error();
        }

        this.panelDefs = this.getMetadata().get(['clientDefs', this.scope, 'relationshipPanels', this.link]) || {};

        if (this.panelDefs.fullFormDisabled) {
            console.error(`Full-form disabled.`);

            throw new Error();
        }

        this.collection.maxSize = this.getConfig().get('recordsPerPage') || this.collection.maxSize;
        this.collectionUrl = this.collection.url;
        this.collectionMaxSize = this.collection.maxSize;

        this.foreignScope = this.collection.entityType;

        this.setupModes();
        this.setViewMode(this.viewMode);

        if (this.getMetadata().get(['clientDefs', this.foreignScope, 'searchPanelDisabled'])) {
            this.searchPanel = false;
        }

        if (this.getUser().isPortal()) {
            if (this.getMetadata().get(['clientDefs', this.foreignScope, 'searchPanelInPortalDisabled'])) {
                this.searchPanel = false;
            }
        }

        if (this.getMetadata().get(['clientDefs', this.foreignScope, 'createDisabled'])) {
            this.createButton = false;
        }

        // noinspection JSUnresolvedReference
        if (
            this.panelDefs.create === false ||
            this.panelDefs.createDisabled ||
            this.panelDefs.createAction
        ) {
            this.createButton = false;
        }

        this.entityType = this.collection.entityType;

        this.headerView = this.options.headerView || this.headerView;
        this.recordView = this.options.recordView || this.recordView;
        this.searchView = this.options.searchView || this.searchView;

        this.setupHeader();

        this.defaultOrderBy = this.panelDefs.orderBy || this.collection.orderBy;
        this.defaultOrder = this.panelDefs.orderDirection || this.collection.order;

        if (this.panelDefs.orderBy && !this.panelDefs.orderDirection) {
            this.defaultOrder = 'asc';
        }

        this.collection.setOrder(this.defaultOrderBy, this.defaultOrder, true);

        if (this.searchPanel) {
            this.setupSearchManager();
        }

        this.setupSorting();

        if (this.searchPanel) {
            this.setupSearchPanel();
        }

        if (this.createButton) {
            this.setupCreateButton();
        }

        if (this.options.params && this.options.params.fromAdmin) {
            this.keepCurrentRootUrl = true;
        }

        this.wait(
            this.getHelper().processSetupHandlers(this, 'list')
        );
    }

    /**
     * Set up modes.
     */
    setupModes() {
        this.defaultViewMode = this.options.defaultViewMode ||
            this.getMetadata().get(['clientDefs', this.foreignScope, 'listRelatedDefaultViewMode']) ||
            this.defaultViewMode;

        this.viewMode = this.viewMode || this.defaultViewMode;

        let viewModeList = this.options.viewModeList ||
            this.viewModeList ||
            this.getMetadata().get(['clientDefs', this.foreignScope, 'listRelatedViewModeList']);

        this.viewModeList = viewModeList ? viewModeList : [this.MODE_LIST];

        if (this.viewModeList.length > 1) {
            let viewMode = null;

            let modeKey = 'listRelatedViewMode' + this.scope + this.link;

            if (this.getStorage().has('state', modeKey)) {
                let storedViewMode = this.getStorage().get('state', modeKey);

                if (storedViewMode && this.viewModeList.includes(storedViewMode)) {
                    viewMode = storedViewMode;
                }
            }

            if (!viewMode) {
                viewMode = this.defaultViewMode;
            }

            this.viewMode = /** @type {string} */viewMode;
        }
    }

    /**
     * Set up a header.
     */
    setupHeader() {
        this.createView('header', this.headerView, {
            collection: this.collection,
            fullSelector: '#main > .page-header',
            scope: this.scope,
            isXsSingleRow: true,
        });
    }

    /**
     * Set up a create button.
     */
    setupCreateButton() {
        this.menu.buttons.unshift({
            action: 'quickCreate',
            iconHtml: '<span class="fas fa-plus fa-sm"></span>',
            text: this.translate('Create ' + this.foreignScope, 'labels', this.foreignScope),
            style: 'default',
            acl: 'create',
            aclScope: this.foreignScope,
            title: 'Ctrl+Space',
        });
    }

    /**
     * Set up a search panel.
     *
     * @protected
     */
    setupSearchPanel() {
        this.createSearchView();
    }

    /**
     * Create a search view.
     *
     * @return {Promise<module:view>}
     * @protected
     */
    createSearchView() {
        let filterList = Espo.Utils
            .clone(this.getMetadata().get(['clientDefs', this.foreignScope, 'filterList']) || []);

        if (this.panelDefs.filterList) {
            this.panelDefs.filterList.forEach(item1 => {
                let isFound = false;
                let name1 = item1.name || item1;

                if (!name1 || name1 === 'all') {
                    return;
                }

                filterList.forEach(item2 => {
                    let name2 = item2.name || item2;

                    if (name1 === name2) {
                        isFound = true;
                    }
                });

                if (!isFound) {
                    filterList.push(item1);
                }
            });
        }

        if (this.filtersDisabled) {
            filterList = [];
        }

        return this.createView('search', this.searchView, {
            collection: this.collection,
            fullSelector: '#main > .search-container',
            searchManager: this.searchManager,
            scope: this.foreignScope,
            viewMode: this.viewMode,
            viewModeList: this.viewModeList,
            isWide: true,
            filterList: filterList,
        }, view => {
            if (this.viewModeList.length > 1) {
                this.listenTo(view, 'change-view-mode', mode => this.switchViewMode(mode));
            }
        });
    }

    /**
     * Switch a view mode.
     *
     * @param {string} mode
     */
    switchViewMode(mode) {
        this.clearView('list');
        this.collection.isFetched = false;
        this.collection.reset();
        this.setViewMode(mode, true);
        this.loadList();
    }

    /**
     * Set a view mode.
     *
     * @param {string} mode A mode.
     * @param {boolean} [toStore=false] To preserve a mode being set.
     */
    setViewMode(mode, toStore) {
        this.viewMode = mode;

        this.collection.url = this.collectionUrl;
        this.collection.maxSize = this.collectionMaxSize;

        if (toStore) {
            var modeKey = 'listViewMode' + this.scope + this.link;

            this.getStorage().set('state', modeKey, mode);
        }

        if (this.searchView && this.getView('search')) {
            this.getSearchView().setViewMode(mode);
        }

        let methodName = 'setViewMode' + Espo.Utils.upperCaseFirst(this.viewMode);

        if (this[methodName]) {
            this[methodName]();
        }
    }

    /**
     * Set up a search manager.
     */
    setupSearchManager() {
        let collection = this.collection;

        let searchManager = new SearchManager(
            collection,
            'list',
            null,
            this.getDateTime(),
            null
        );

        searchManager.scope = this.foreignScope;

        collection.where = searchManager.getWhere();

        this.searchManager = searchManager;
    }

    /**
     * Set up sorting.
     */
    setupSorting() {}

    /**
     * @protected
     * @return {module:views/record/search}
     */
    getSearchView() {
        return this.getView('search');
    }

    /**
     * @protected
     * @return {module:view}
     */
    getRecordView() {
        return this.getView('list');
    }

    /**
     * Get a record view name.
     *
     * @returns {string}
     */
    getRecordViewName() {
        if (this.viewMode === this.MODE_LIST) {
            return this.panelDefs.recordListView ||
                this.getMetadata().get(['clientDefs', this.foreignScope, 'recordViews', this.MODE_LIST]) ||
                    this.recordView;
        }

        let propertyName = 'record' + Espo.Utils.upperCaseFirst(this.viewMode) + 'View';

        return this.getMetadata().get(['clientDefs', this.foreignScope, 'recordViews', this.viewMode]) ||
            this[propertyName];
    }

    /**
     * @inheritDoc
     */
    afterRender() {
        Espo.Ui.notify(false);

        if (!this.hasView('list')) {
            this.loadList();
        }

        // noinspection JSUnresolvedReference
        this.$el.get(0).focus({preventScroll: true});
    }

    /**
     * Load a record list view.
     */
    loadList() {
        if ('isFetched' in this.collection && this.collection.isFetched) {
            this.createListRecordView(false);

            return;
        }

        Espo.Ui.notify(' ... ');

        this.createListRecordView(true);
    }

    /**
     * Prepare record view options. Options can be modified in an extended method.
     *
     * @param {Object} options Options
     */
    prepareRecordViewOptions(options) {}

    /**
     * Create a record list view.
     */
    createListRecordView() {
        let o = {
            collection: this.collection,
            selector: '.list-container',
            scope: this.foreignScope,
            skipBuildRows: true,
            shortcutKeysEnabled: true,
        };

        this.optionsToPass.forEach(option => {
            o[option] = this.options[option];
        });

        if (this.keepCurrentRootUrl) {
            o.keepCurrentRootUrl = true;
        }

        if (this.panelDefs.layout && typeof this.panelDefs.layout === 'string') {
            o.layoutName = this.panelDefs.layout;
        }

        o.rowActionsView = this.panelDefs.readOnly ? false :
            (this.panelDefs.rowActionsView || this.rowActionsView);

        if (
            this.getConfig().get('listPagination') ||
            this.getMetadata().get(['clientDefs', this.foreignScope, 'listPagination'])
        ) {
            o.pagination = true;
        }

        let massUnlinkDisabled = this.panelDefs.massUnlinkDisabled ||
            this.panelDefs.unlinkDisabled || this.unlinkDisabled;

        o = {
            unlinkMassAction: !massUnlinkDisabled,
            skipBuildRows: true,
            buttonsDisabled: true,
            forceDisplayTopBar: true,
            rowActionsOptions:  {
                unlinkDisabled: this.panelDefs.unlinkDisabled || this.unlinkDisabled,
            },
            ...o
        };

        if (this.getHelper().isXsScreen()) {
            o.type = 'listSmall';
        }

        this.prepareRecordViewOptions(o);

        let listViewName = this.getRecordViewName();

        this.createView('list', listViewName, o, view =>{
            if (!this.hasParentView()) {
                view.undelegateEvents();

                return;
            }

            this.listenToOnce(view, 'after:render', () => {
                if (!this.hasParentView()) {
                    view.undelegateEvents();

                    this.clearView('list');
                }
            });

            view.getSelectAttributeList(selectAttributeList => {
                if (this.options.mediator && this.options.mediator.abort) {
                    return;
                }

                if (selectAttributeList) {
                    this.collection.data.select = selectAttributeList.join(',');
                }

                Espo.Ui.notify(' ... ');

                this.collection.fetch({main: true})
                    .then(() => Espo.Ui.notify(false));
            });
        });
    }

    /**
     * A quick-create action.
     *
     * @protected
     */
    actionQuickCreate() {
        let link = this.link;
        let foreignScope = this.foreignScope;
        let foreignLink = this.model.getLinkParam(link, 'foreign');

        let attributes = {};

        let attributeMap = this.getMetadata()
                .get(['clientDefs', this.scope, 'relationshipPanels', link, 'createAttributeMap']) || {};

        Object.keys(attributeMap)
            .forEach(attr => {
                attributes[attributeMap[attr]] = this.model.get(attr);
            });

        Espo.Ui.notify(' ... ');

        let handler = this.getMetadata()
            .get(['clientDefs', this.scope, 'relationshipPanels', link, 'createHandler']);

        (new Promise(resolve => {
            if (!handler) {
                resolve({});

                return;
            }

            Espo.loader.requirePromise(handler)
                .then(Handler => new Handler(this.getHelper()))
                .then(handler => {
                    handler.getAttributes(this.model)
                        .then(attributes => resolve(attributes));
                });
        }))
            .then(additionalAttributes => {
                attributes = {...attributes, ...additionalAttributes};

                let viewName = this.getMetadata()
                    .get(['clientDefs', foreignScope, 'modalViews', 'edit']) || 'views/modals/edit';

                this.createView('quickCreate', viewName, {
                    scope: foreignScope,
                    relate: {
                        model: this.model,
                        link: foreignLink,
                    },
                    attributes: attributes,
                }, view => {
                    view.render();
                    view.notify(false);

                    this.listenToOnce(view, 'after:save', () => {
                        this.collection.fetch();

                        this.model.trigger('after:relate');
                        this.model.trigger('after:relate:' + link);
                    });
                });
            });
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * An `unlink-related` action.
     *
     * @protected
     */
    actionUnlinkRelated(data) {
        let id = data.id;

        this.confirm({
            message: this.translate('unlinkRecordConfirmation', 'messages'),
            confirmText: this.translate('Unlink'),
        }, () => {
            Espo.Ui.notify(' ... ');

            Espo.Ajax
                .deleteRequest(this.collection.url, {id: id})
                .then(() => {
                    Espo.Ui.success(this.translate('Unlinked'));

                    this.collection.fetch();

                    this.model.trigger('after:unrelate');
                    this.model.trigger('after:unrelate:' + this.link);
                });
        });
    }

    /**
     * @inheritDoc
     */
    getHeader() {
        let name = this.model.get('name') || this.model.id;

        let recordUrl = '#' + this.scope  + '/view/' + this.model.id;

        let $name =
            $('<a>')
                .attr('href', recordUrl)
                .addClass('font-size-flexible title')
                .text(name);

        if (this.model.get('deleted')) {
            $name.css('text-decoration', 'line-through');
        }

        let headerIconHtml = this.getHelper().getScopeColorIconHtml(this.foreignScope);
        let scopeLabel = this.getLanguage().translate(this.scope, 'scopeNamesPlural');

        let $root = $('<span>').text(scopeLabel);

        if (!this.rootLinkDisabled) {
            $root = $('<span>')
                .append(
                    $('<a>')
                        .attr('href', '#' + this.scope)
                        .addClass('action')
                        .attr('data-action', 'navigateToRoot')
                        .text(scopeLabel)
                );
        }

        if (headerIconHtml) {
            $root.prepend(headerIconHtml);
        }

        let $link = $('<span>').text(this.translate(this.link, 'links', this.scope));

        return this.buildHeaderHtml([
            $root,
            $name,
            $link
        ]);
    }

    /**
     * @inheritDoc
     */
    updatePageTitle() {
        this.setPageTitle(this.getLanguage().translate(this.link, 'links', this.scope));
    }

    /**
     * Create attributes for an entity being created.
     *
     * @return {Object}
     */
    getCreateAttributes() {}

    /**
     * @protected
     * @param {JQueryKeyEventObject} e
     */
    handleShortcutKeyCtrlSpace(e) {
        if (!this.createButton) {
            return;
        }

        if (!this.getAcl().checkScope(this.foreignScope, 'create')) {
            return;
        }

        e.preventDefault();
        e.stopPropagation();


        this.actionQuickCreate({focusForCreate: true});
    }

    /**
     * @protected
     * @param {JQueryKeyEventObject} e
     */
    handleShortcutKeyCtrlSlash(e) {
        if (!this.searchPanel) {
            return;
        }

        let $search = this.$el.find('input.text-filter').first();

        if (!$search.length) {
            return;
        }

        e.preventDefault();
        e.stopPropagation();

        $search.focus();
    }

    // noinspection JSUnusedLocalSymbols
    /**
     * @protected
     * @param {JQueryKeyEventObject} e
     */
    handleShortcutKeyCtrlComma(e) {
        if (!this.getSearchView()) {
            return;
        }

        this.getSearchView().selectPreviousPreset();
    }

    // noinspection JSUnusedLocalSymbols
    /**
     * @protected
     * @param {JQueryKeyEventObject} e
     */
    handleShortcutKeyCtrlPeriod(e) {
        if (!this.getSearchView()) {
            return;
        }

        this.getSearchView().selectNextPreset();
    }
}

export default ListRelatedView;
PK]۔�##views/stream.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';

class StreamView extends View {

    template ='stream'
    filterList = ['all', 'posts', 'updates']
    filter = false

    events = {
        /** @this StreamView */
        'click button[data-action="refresh"]': function () {
            if (!this.getRecordView()) {
                return;
            }

            this.getRecordView().showNewRecords();
        },
        /** @this StreamView */
        'click button[data-action="selectFilter"]': function (e) {
            let data = $(e.currentTarget).data();

            this.actionSelectFilter(data);
        },
    }

    data() {
        let filter = this.filter;

        if (filter === false) {
            filter = 'all';
        }

        return {
            displayTitle: this.options.displayTitle,
            filterList: this.filterList,
            filter: filter,
        };
    }

    setup() {
        this.filter = this.options.filter || this.filter;

        this.wait(
            this.getModelFactory().create('Note', model => {
                this.createView('createPost', 'views/stream/record/edit', {
                    selector: '.create-post-container',
                    model: model,
                    interactiveMode: true,
                }, view => {
                    this.listenTo(view, 'after:save', () => this.getRecordView().showNewRecords());
                });
            })
        );
    }

    afterRender() {
        Espo.Ui.notify(' ... ');

        this.getCollectionFactory().create('Note', collection => {
            this.collection = collection;
            collection.url = 'Stream';

            this.setFilter(this.filter);

            collection.fetch().then(() => {
                this.createView('list', 'views/stream/record/list', {
                    selector: '.list-container',
                    collection: collection,
                    isUserStream: true,
                }, view => {
                    view.notify(false);

                    view.render()
                        .then(view => {
                            view.$el.find('> .list > .list-group');
                        });
                });
            });
        });
    }

    /**
     * @return {module:views/stream/record/list}
     */
    getRecordView() {
        return this.getView('list');
    }

    actionSelectFilter(data) {
        let name = data.name;
        let filter = name;

        let internalFilter = name;

        if (filter === 'all') {
            internalFilter = false;
        }

        this.filter = internalFilter;
        this.setFilter(this.filter);

        this.filterList.forEach((item) => {
            var $el = this.$el.find('.page-header button[data-action="selectFilter"][data-name="'+item+'"]');

            if (item === filter) {
                $el.addClass('active');
            } else {
                $el.removeClass('active');
            }
        });

        let url = '#Stream';

        if (this.filter) {
            url += '/' + filter;
        }

        this.getRouter().navigate(url);

        Espo.Ui.notify(' ... ');

        this.listenToOnce(this.collection, 'sync', () => {
            Espo.Ui.notify(false);
        });

        this.collection.reset();
        this.collection.fetch();
    }

    setFilter(filter) {
        this.collection.data.filter = null;

        if (filter) {
            this.collection.data.filter = filter;
        }

        this.collection.offset = 0;
        this.collection.maxSize = this.getConfig().get('recordsPerPage') || this.collection.maxSize;
    }
}

export default StreamView;
PK]���TT*views/fields/foreign-currency-converted.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import CurrencyConvertedFieldView from 'views/fields/currency-converted';

class ForeignCurrencyConvertedFieldView extends CurrencyConvertedFieldView {

    type = 'foreign'
}

export default ForeignCurrencyConvertedFieldView;
PK]6�-	-	views/fields/assigned-users.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import LinkMultipleFieldView from 'views/fields/link-multiple';

class AssignedUsersFieldView extends LinkMultipleFieldView {

    init() {
        this.assignmentPermission = this.getAcl().getPermissionLevel('assignmentPermission');

        if (this.assignmentPermission === 'no') {
            this.readOnly = true;
        }

        super.init();
    }

    getSelectBoolFilterList() {
        if (this.assignmentPermission === 'team') {
            return ['onlyMyTeam'];
        }
    }

    getSelectPrimaryFilterName() {
        return 'active';
    }

    getDetailLinkHtml(id, name) {
        let html = super.getDetailLinkHtml(id);

        let avatarHtml = this.isDetailMode() ?
            this.getHelper().getAvatarHtml(id, 'small', 14, 'avatar-link') : '';

        if (!avatarHtml) {
            return html;
        }

        return avatarHtml + ' ' + html;
    }
}

export default AssignedUsersFieldView;
PK]�iǩ�
�
views/fields/foreign-array.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ArrayFieldView from 'views/fields/array';

class ForeignArrayFieldView extends ArrayFieldView {

    type = 'foreign'

    setupOptions() {
        this.params.options = [];

        let field = this.params.field;
        let link = this.params.link;

        if (!field || !link) {
            return;
        }

        let scope = this.getMetadata().get(['entityDefs', this.model.entityType, 'links', link, 'entity']);

        if (!scope) {
            return;
        }

        let {
            optionsPath,
            translation,
            options,
            isSorted,
            displayAsLabel,
            style,
        } = this.getMetadata()
            .get(['entityDefs', scope, 'fields', field]);

        options = optionsPath ? this.getMetadata().get(optionsPath) : options;

        this.params.options = Espo.Utils.clone(options) || [];
        this.params.translation = translation;
        this.params.isSorted = isSorted || false;
        this.params.displayAsLabel = displayAsLabel || false;
        this.styleMap = style || {};

        this.translatedOptions = Object.fromEntries(
            this.params.options
                .map(item => [item, this.getLanguage().translateOption(item, field, scope)])
        );
    }
}

export default ForeignArrayFieldView;
PK]�@w`T`Tviews/fields/address.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/fields/address */

import BaseFieldView from 'views/fields/base';
import Varchar from 'views/fields/varchar';

/**
 * An address field.
 */
class AddressFieldView extends BaseFieldView {

    type = 'address'

    listTemplate = 'fields/address/detail'
    detailTemplate = 'fields/address/detail'
    editTemplate = 'fields/address/edit'
    editTemplate1 = 'fields/address/edit-1'
    editTemplate2 = 'fields/address/edit-2'
    editTemplate3 = 'fields/address/edit-3'
    editTemplate4 = 'fields/address/edit-4'
    searchTemplate = 'fields/address/search'

    postalCodeField
    streetField
    cityField
    stateField
    countryField

    /** @inheritDoc */
    validations = [
        'required',
        'pattern',
    ]

    /** @inheritDoc */
    events = {
        /** @this AddressFieldView */
        'click [data-action="viewMap"]': function (e) {
            e.preventDefault();
            e.stopPropagation();

            this.viewMapAction();
        },
    }

    data() {
        let data = super.data();

        data.ucName = Espo.Utils.upperCaseFirst(this.name);

        this.addressPartList.forEach(item => {
            data[item + 'Value'] = this.model.get(this[item + 'Field']);
        });

        if (this.mode === this.MODE_DETAIL || this.mode === this.MODE_LIST) {
            data.formattedAddress = this.getFormattedAddress();

            data.isNone = data.formattedAddress === null;

            if (data.formattedAddress === -1) {
                data.formattedAddress = null;
                data.isLoading = true;
            }

            if (this.params.viewMap && this.canBeDisplayedOnMap()) {
                data.viewMap = true;

                data.viewMapLink = '#AddressMap/view/' +
                    this.model.entityType + '/' +
                    this.model.id + '/' +
                    this.name;
            }
        }

        if (this.isEditMode()) {
            data.stateMaxLength = this.stateMaxLength;
            data.streetMaxLength = this.streetMaxLength;
            data.postalCodeMaxLength = this.postalCodeMaxLength;
            data.cityMaxLength = this.cityMaxLength;
            data.countryMaxLength = this.countryMaxLength;
        }

        return data;
    }

    setupSearch() {
        this.searchData.value = this.getSearchParamsData().value || this.searchParams.additionalValue;
    }

    canBeDisplayedOnMap() {
        return !!this.model.get(this.name + 'City') || !!this.model.get(this.name + 'PostalCode');
    }

    getFormattedAddress() {
        let isNotEmpty = false;
        let isSet = false;

        this.addressAttributeList.forEach(attribute => {
            isNotEmpty = isNotEmpty || this.model.get(attribute);
            isSet = isSet || this.model.has(attribute);
        });

        let isEmpty = !isNotEmpty;

        if (isEmpty) {
            if (this.mode === this.MODE_LIST) {
                return '';
            }

            if (!isSet) {
                return -1;
            }

            return null;
        }

        let methodName = 'getFormattedAddress' + this.getAddressFormat().toString();

        if (methodName in this) {
            return this[methodName]();
        }
    }

    getFormattedAddress1() {
        let postalCodeValue = this.model.get(this.postalCodeField);
        let streetValue = this.model.get(this.streetField);
        let cityValue = this.model.get(this.cityField);
        let stateValue = this.model.get(this.stateField);
        let countryValue = this.model.get(this.countryField);

        let html = '';

        if (streetValue) {
            html += streetValue;
        }

        if (cityValue || stateValue || postalCodeValue) {
            if (html !== '') {
                html += '\n';
            }

            if (cityValue) {
                html += cityValue;
            }

            if (stateValue) {
                if (cityValue) {
                    html += ', ';
                }
                html += stateValue;
            }

            if (postalCodeValue) {
                if (cityValue || stateValue) {
                    html += ' ';
                }
                html += postalCodeValue;
            }
        }
        if (countryValue) {
            if (html !== '') {
                html += '\n';
            }

            html += countryValue;
        }

        return html;
    }

    getFormattedAddress2() {
        let postalCodeValue = this.model.get(this.postalCodeField);
        let streetValue = this.model.get(this.streetField);
        let cityValue = this.model.get(this.cityField);
        let stateValue = this.model.get(this.stateField);
        let countryValue = this.model.get(this.countryField);

        let html = '';

        if (streetValue) {
            html += streetValue;
        }

        if (cityValue || postalCodeValue) {
            if (html !== '') {
                html += '\n';
            }

            if (postalCodeValue) {
                html += postalCodeValue;

                if (cityValue) {
                    html += ' ';
                }
            }

            if (cityValue) {
                html += cityValue;
            }
        }

        if (stateValue || countryValue) {
            if (html !== '') {
                html += '\n';
            }

            if (stateValue) {
                html += stateValue;

                if (countryValue) {
                    html += ' ';
                }
            }

            if (countryValue) {
                html += countryValue;
            }
        }

        return html;
    }

    getFormattedAddress3() {
        let postalCodeValue = this.model.get(this.postalCodeField);
        let streetValue = this.model.get(this.streetField);
        let cityValue = this.model.get(this.cityField);
        let stateValue = this.model.get(this.stateField);
        let countryValue = this.model.get(this.countryField);

        let html = '';

        if (countryValue) {
            html += countryValue;
        }

        if (cityValue || stateValue || postalCodeValue) {
            if (html !== '') {
                html += '\n';
            }

            if (postalCodeValue) {
                html += postalCodeValue;
            }

            if (stateValue) {
                if (postalCodeValue) {
                    html += ' ';
                }
                html += stateValue;
            }

            if (cityValue) {
                if (postalCodeValue || stateValue) {
                    html += ' ';
                }
                html += cityValue;
            }
        }
        if (streetValue) {
            if (html !== '') {
                html += '\n';
            }

            html += streetValue;
        }

        return html;
    }

    getFormattedAddress4() {
        let postalCodeValue = this.model.get(this.postalCodeField);
        let streetValue = this.model.get(this.streetField);
        let cityValue = this.model.get(this.cityField);
        let stateValue = this.model.get(this.stateField);
        let countryValue = this.model.get(this.countryField);

        let html = '';

        if (streetValue) {
            html += streetValue;
        }

        if (cityValue) {
            if (html !== '') {
                html += '\n';
            }

            html += cityValue;
        }

        if (countryValue || stateValue || postalCodeValue) {
            if (html !== '') {
                html += '\n';
            }

            if (countryValue) {
                html += countryValue;
            }

            if (stateValue) {
                if (countryValue) {
                    html += ' - ';
                }

                html += stateValue;
            }

            if (postalCodeValue) {
                if (countryValue || stateValue) {
                    html += ' ';
                }

                html += postalCodeValue;
            }
        }

        return html;
    }

    _getTemplateName() {
        if (this.mode === this.MODE_EDIT) {
            let prop = 'editTemplate' + this.getAddressFormat().toString();

            if (prop in this) {
                return this[prop];
            }
        }

        return super._getTemplateName();
    }

    getAddressFormat() {
        return this.getConfig().get('addressFormat') || 1;
    }

    afterRender() {
        if (this.mode === this.MODE_EDIT) {
            this.$street = this.$el.find('[data-name="' + this.streetField + '"]');
            this.$postalCode = this.$el.find('[data-name="' + this.postalCodeField + '"]');
            this.$state = this.$el.find('[data-name="' + this.stateField + '"]');
            this.$city = this.$el.find('[data-name="' + this.cityField + '"]');
            this.$country = this.$el.find('[data-name="' + this.countryField + '"]');

            this.$street.on('change', () => {
                this.trigger('change');
            });

            this.$postalCode.on('change', () => {
                this.trigger('change');
            });

            this.$state.on('change', () => {
                this.trigger('change');
            });

            this.$city.on('change', () => {
                this.trigger('change');
            });

            this.$country.on('change', () => {
                this.trigger('change');
            });

            let countryList = this.getConfig().get('addressCountryList') || [];

            if (countryList.length) {
                this.$country.autocomplete({
                    minChars: 0,
                    lookup: countryList,
                    maxHeight: 200,
                    formatResult: suggestion => {
                        return this.getHelper().escapeString(suggestion.value);
                    },
                    lookupFilter: (suggestion, query, queryLowerCase) => {
                        if (suggestion.value.toLowerCase().indexOf(queryLowerCase) === 0) {
                            if (suggestion.value.length === queryLowerCase.length) {
                                return false;
                            }

                            return true;
                        }

                        return false;
                    },
                    onSelect: () => {
                        this.trigger('change');

                        this.$country.focus();
                    },
                });

                this.$country.on('focus', () => {
                    if (this.$country.val()) {
                        return;
                    }

                    this.$country.autocomplete('onValueChange');
                });

                this.once('render', () => {
                    this.$country.autocomplete('dispose');
                });

                this.once('remove', () => {
                    this.$country.autocomplete('dispose');
                });

                this.$country.attr('autocomplete', 'espo-country');
            }

            let cityList = this.getConfig().get('addressCityList') || [];

            if (cityList.length) {
                this.$city.autocomplete({
                    minChars: 0,
                    lookup: cityList,
                    maxHeight: 200,
                    formatResult: (suggestion) => {
                        return this.getHelper().escapeString(suggestion.value);
                    },
                    lookupFilter: (suggestion, query, queryLowerCase) => {
                        if (suggestion.value.toLowerCase().indexOf(queryLowerCase) === 0) {
                            if (suggestion.value.length === queryLowerCase.length) {
                                return false;
                            }

                            return true;
                        }

                        return false;
                    },
                    onSelect: () => {
                        this.trigger('change');

                        this.$city.focus();
                    },
                });

                this.$city.on('focus', () => {
                    if (this.$city.val()) {
                        return;
                    }

                    this.$city.autocomplete('onValueChange');
                });

                this.once('render', () => {
                    this.$city.autocomplete('dispose');
                });

                this.once('remove', () => {
                    this.$city.autocomplete('dispose');
                });

                this.$city.attr('autocomplete', 'espo-city');
            }

            let stateList = this.getConfig().get('addressStateList') || [];

            if (stateList.length) {
                this.$state.autocomplete({
                    minChars: 0,
                    lookup: stateList,
                    maxHeight: 200,
                    formatResult: suggestion => {
                        return this.getHelper().escapeString(suggestion.value);
                    },
                    lookupFilter: function (suggestion, query, queryLowerCase) {
                        if (suggestion.value.toLowerCase().indexOf(queryLowerCase) === 0) {
                            if (suggestion.value.length === queryLowerCase.length) {
                                return false;
                            }

                            return true;
                        }

                        return false;
                    },
                    onSelect: () => {
                        this.trigger('change');

                        this.$state.focus();
                    },
                });

                this.$state.on('focus', () => {
                    if (this.$state.val()) {
                        return;
                    }

                    this.$state.autocomplete('onValueChange');
                });

                this.once('render', () => {
                    this.$state.autocomplete('dispose');
                });

                this.once('remove', () => {
                    this.$state.autocomplete('dispose');
                });

                this.$state.attr('autocomplete', 'espo-state');
            }

            this.controlStreetTextareaHeight();

            this.$street.on('input', () => {
                this.controlStreetTextareaHeight();
            });
        }
    }

    controlStreetTextareaHeight(lastHeight) {
        let scrollHeight = this.$street.prop('scrollHeight');
        let clientHeight = this.$street.prop('clientHeight');

        if (typeof lastHeight === 'undefined' && clientHeight === 0) {
            setTimeout(this.controlStreetTextareaHeight.bind(this), 10);

            return;
        }

        if (clientHeight === lastHeight) return;

        if (scrollHeight > clientHeight + 1) {
            let rows = this.$street.prop('rows');
            this.$street.attr('rows', rows + 1);

            this.controlStreetTextareaHeight(clientHeight);
        }

        if (this.$street.val().length === 0) {
            this.$street.attr('rows', 1);
        }
    }

    setup() {
        super.setup();

        let actualAttributePartList = this.getMetadata().get(['fields', this.type, 'actualFields']) || [];

        this.addressAttributeList = [];
        this.addressPartList = [];

        actualAttributePartList.forEach(item => {
            let attribute = this.name + Espo.Utils.upperCaseFirst(item);

            this.addressAttributeList.push(attribute);
            this.addressPartList.push(item);

            this[item + 'Field'] = attribute;

            this[item + 'MaxLength'] =
                this.getMetadata().get(['entityDefs', this.entityType, 'fields', attribute, 'maxLength']);
        });
    }

    validateRequired() {
        let validate = name => {
            if (this.model.isRequired(name)) {
                if (this.model.get(name) === '') {
                    let msg = this.translate('fieldIsRequired', 'messages')
                        .replace('{field}', this.translate(name, 'fields', this.entityType));

                    this.showValidationMessage(msg, '[data-name="'+name+'"]');

                    return true;
                }
            }
        };

        let result = false;

        result = validate(this.postalCodeField) || result;
        result = validate(this.streetField) || result;
        result = validate(this.stateField) || result;
        result = validate(this.cityField) || result;
        result = validate(this.countryField) || result;

        return result;
    }

    isRequired() {
        return this.model.getFieldParam(this.postalCodeField, 'required') ||
            this.model.getFieldParam(this.streetField, 'required') ||
            this.model.getFieldParam(this.stateField, 'required') ||
            this.model.getFieldParam(this.cityField, 'required') ||
            this.model.getFieldParam(this.countryField, 'required');
    }

    validatePattern() {
        let fieldList = [
            this.postalCodeField,
            this.stateField,
            this.cityField,
            this.countryField,
        ];

        let result = false;

        for (let field of fieldList) {
            result = Varchar.prototype.fieldValidatePattern.call(this, field) || result;
        }

        return result;
    }

    fetch() {
        let data = {};

        data[this.postalCodeField] = this.$postalCode.val().toString().trim();
        data[this.streetField] = this.$street.val().toString().trim();
        data[this.stateField] = this.$state.val().toString().trim();
        data[this.cityField] = this.$city.val().toString().trim();
        data[this.countryField] = this.$country.val().toString().trim();

        let attributeList = [
            this.postalCodeField,
            this.streetField,
            this.stateField,
            this.cityField,
            this.countryField,
        ];

        attributeList.forEach(attribute => {
            if (data[attribute] === '') {
                data[attribute] = null;
            }
        });

        return data;
    }

    fetchSearch() {
        let value = this.$el.find('input.main-element')
            .val()
            .toString()
            .trim();

        if (!value) {
            return null;
        }

        return {
            type: 'or',
            value: [
                {
                    type: 'like',
                    field: this.postalCodeField,
                    value: value + '%'
                },
                {
                    type: 'like',
                    field: this.streetField,
                    value: value + '%'
                },
                {
                    type: 'like',
                    field: this.cityField,
                    value: value + '%'
                },
                {
                    type: 'like',
                    field: this.stateField,
                    value: value + '%'
                },
                {
                    type: 'like',
                    field: this.countryField,
                    value: value + '%'
                }
            ],
            data: {
                value: value
            }
        };
    }

    viewMapAction() {
        this.createView('mapDialog', 'views/modals/view-map', {
            model: this.model,
            field: this.name,
        }, view => view.render());
    }
}

export default AddressFieldView;
PK])ԩ�views/fields/password.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import BaseFieldView from 'views/fields/base';

class PasswordFieldView extends BaseFieldView {

    type = 'password'

    detailTemplate = 'fields/password/detail'
    editTemplate = 'fields/password/edit'

    validations = ['required', 'confirm']

    events = {
        /** @this PasswordFieldView */
        'click [data-action="change"]': function () {
            this.changePassword();
        },
    }

    changePassword() {
        this.$el.find('[data-action="change"]').addClass('hidden');
        this.$element.removeClass('hidden');

        this.changing = true;
    }

    /** @inheritDoc */
    data() {
        return {
            isNew: this.model.isNew(),
            ...super.data(),
        }
    }

    // noinspection JSUnusedGlobalSymbols
    validateConfirm() {
        if (!this.model.has(this.name + 'Confirm')) {
            return;
        }

        if (this.model.get(this.name) !== this.model.get(this.name + 'Confirm')) {
            let msg = this.translate('fieldBadPasswordConfirm', 'messages')
                .replace('{field}', this.getLabelText());

            this.showValidationMessage(msg);

            return true;
        }
    }

    afterRender() {
        super.afterRender();

        this.changing = false;

        if (this.params.readyToChange) {
            this.changePassword();
        }
    }

    fetch() {
        if (!this.model.isNew() && !this.changing) {
            return {};
        }

        return super.fetch();
    }
}

export default PasswordFieldView;
PK]���7views/fields/link-multiple-with-columns-with-primary.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import LinkMultipleWithColumnsFieldView from 'views/fields/link-multiple-with-columns';
import LinkMultipleWithPrimaryFieldView from 'views/fields/link-multiple-with-primary';

/**
 * A link-multiple field with columns and a primary.
 */
class LinkMultipleWithColumnsWithPrimaryFieldView extends LinkMultipleWithColumnsFieldView {

    /**
     * @protected
     * @type {string}
     */
    primaryLink

    getAttributeList() {
        let list = super.getAttributeList();

        list.push(this.primaryIdAttribute);
        list.push(this.primaryNameAttribute);

        return list;
    }

    setup() {
        this.primaryLink = this.primaryLink || this.model.getFieldParam(this.name, 'primaryLink');

        this.primaryIdAttribute = this.primaryLink + 'Id';
        this.primaryNameAttribute = this.primaryLink + 'Name';

        super.setup();

        this.events['click [data-action="switchPrimary"]'] = e => {
            let $target = $(e.currentTarget);
            let id = $target.data('id');

            LinkMultipleWithPrimaryFieldView.prototype.switchPrimary.call(this, id);
        };

        this.primaryId = this.model.get(this.primaryIdAttribute);
        this.primaryName = this.model.get(this.primaryNameAttribute);

        this.listenTo(this.model, 'change:' + this.primaryIdAttribute, () => {
            this.primaryId = this.model.get(this.primaryIdAttribute);
            this.primaryName = this.model.get(this.primaryNameAttribute);
        });
    }

    setPrimaryId(id) {
        this.primaryId = id;

        this.primaryName = id ?
            this.nameHash[id] : null;

        this.trigger('change');
    }

    renderLinks() {
        if (this.primaryId) {
            this.addLinkHtml(this.primaryId, this.primaryName);
        }

        this.ids.forEach(id => {
            if (id !== this.primaryId) {
                this.addLinkHtml(id, this.nameHash[id]);
            }
        });
    }

    getValueForDisplay() {
        if (this.isDetailMode() || this.isListMode()) {
            let itemList = [];

            if (this.primaryId) {
                itemList.push(
                    this.getDetailLinkHtml(this.primaryId, this.primaryName)
                );
            }

            if (!this.ids.length) {
                return;
            }

            this.ids.forEach(id =>{
                if (id !== this.primaryId) {
                    itemList.push(
                        this.getDetailLinkHtml(id)
                    );
                }
            });

            return itemList
                .map(item => $('<div>').append(item).get(0).outerHTML)
                .join('');
        }
    }

    deleteLink(id) {
        if (id === this.primaryId) {
            this.setPrimaryId(null);
        }

        super.deleteLink(id);
    }

    deleteLinkHtml(id) {
        super.deleteLinkHtml(id);

        this.managePrimaryButton();
    }

    addLinkHtml(id, name) {
        name = name || id;

        if (this.isSearchMode()) {
            return super.addLinkHtml(id, name);
        }

        if (this.skipRoles) {
            return LinkMultipleWithPrimaryFieldView.prototype.addLinkHtml.call(this, id, name);
        }

        let $el = super.addLinkHtml(id, name);

        let isPrimary = (id === this.primaryId);

        let $star = $('<span>')
            .addClass('fas fa-star fa-sm')
            .addClass(!isPrimary ? 'text-muted' : '')

        let $button = $('<button>')
            .attr('type', 'button')
            .addClass('btn btn-link btn-sm pull-right hidden')
            .attr('title', this.translate('Primary'))
            .attr('data-action', 'switchPrimary')
            .attr('data-id', id)
            .append($star);

        $button.insertAfter($el.children().first().children().first());

        this.managePrimaryButton();

        return $el;
    }

    managePrimaryButton() {
        let $primary = this.$el.find('button[data-action="switchPrimary"]');

        if ($primary.length > 1) {
            $primary.removeClass('hidden');
        } else {
            $primary.addClass('hidden');
        }

        if ($primary.filter('.active').length === 0) {
            let $first = $primary.first();

            if ($first.length) {
                $first.addClass('active').children().removeClass('text-muted');
                this.setPrimaryId($first.data('id'));
            }
        }
    }

    fetch() {
        const data = super.fetch();

        data[this.primaryIdAttribute] = this.primaryId;
        data[this.primaryNameAttribute] = this.primaryName;

        return data;
    }
}

// noinspection JSUnusedGlobalSymbols
export default LinkMultipleWithColumnsWithPrimaryFieldView;
PK])#���� views/fields/user-with-avatar.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import UserFieldView from 'views/fields/user';

class UserWithAvatarFieldView extends UserFieldView {

    listTemplate = 'fields/user-with-avatar/list'
    detailTemplate = 'fields/user-with-avatar/detail'

    data() {
        let o = super.data();

        if (this.mode === this.MODE_DETAIL) {
            o.avatar = this.getAvatarHtml();
            o.isOwn = this.model.get(this.idName) === this.getUser().id;
        }

        return o;
    }

    getAvatarHtml() {
        return this.getHelper().getAvatarHtml(this.model.get(this.idName), 'small', 14, 'avatar-link');
    }
}

export default UserWithAvatarFieldView;
PK]%����views/fields/users.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import LinkMultipleFieldView from 'views/fields/link-multiple';

class UsersFieldView extends LinkMultipleFieldView {

    init() {
        this.assignmentPermission = this.getAcl().getPermissionLevel('assignmentPermission');

        if (this.assignmentPermission === 'no') {
            this.readOnly = true;
        }

        super.init();
    }

    getSelectBoolFilterList() {
        if (this.assignmentPermission === 'team' || this.assignmentPermission === 'no') {
            return ['onlyMyTeam'];
        }
    }

    getSelectPrimaryFilterName() {
        return 'active';
    }
}

export default UsersFieldView;


PK]��,��@�@views/fields/phone.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import VarcharFieldView from 'views/fields/varchar';
import Select from 'ui/select';

class PhoneFieldView extends VarcharFieldView {

    type = 'phone'

    editTemplate = 'fields/phone/edit'
    detailTemplate = 'fields/phone/detail'
    listTemplate = 'fields/phone/list'

    validations = ['required', 'phoneData']

    events = {
        /** @this PhoneFieldView */
        'click [data-action="switchPhoneProperty"]': function (e) {
            let $target = $(e.currentTarget);
            let $block = $(e.currentTarget).closest('div.phone-number-block');
            let property = $target.data('property-type');

            if (property === 'primary') {
                if (!$target.hasClass('active')) {
                    if ($block.find('input.phone-number').val() !== '') {
                        this.$el.find('button.phone-property[data-property-type="primary"]')
                            .removeClass('active').children().addClass('text-muted');

                        $target.addClass('active').children().removeClass('text-muted');
                    }
                }
            }
            else {
                if ($target.hasClass('active')) {
                    $target.removeClass('active').children().addClass('text-muted');
                } else {
                    $target.addClass('active').children().removeClass('text-muted');
                }
            }

            this.trigger('change');
        },
        /** @this PhoneFieldView */
        'click [data-action="removePhoneNumber"]': function (e) {
            let $block = $(e.currentTarget).closest('div.phone-number-block');

            this.removePhoneNumber($block);

            this.trigger('change');

            let $last = this.$el.find('.phone-number').last();

            if ($last.length) {
                $last[0].focus({preventScroll: true});
            }
        },
        /** @this PhoneFieldView */
        'change input.phone-number': function (e) {
            let $input = $(e.currentTarget);
            let $block = $input.closest('div.phone-number-block');

            if (this._itemJustRemoved) {
                return;
            }

            if ($input.val() === '' && $block.length) {
                this.removePhoneNumber($block);
            }
            else {
                this.trigger('change');
            }

            this.manageAddButton();
        },
        /** @this PhoneFieldView */
        'keypress input.phone-number': function () {
            this.manageAddButton();
        },
        /** @this PhoneFieldView */
        'paste input.phone-number': function () {
            setTimeout(() => this.manageAddButton(), 10);
        },
        /** @this PhoneFieldView */
        'click [data-action="addPhoneNumber"]': function () {
            this.addPhoneNumber();
        },
        /** @this PhoneFieldView */
        'keydown input.phone-number': function (e) {
            let key = Espo.Utils.getKeyFromKeyEvent(e);

            let $target = $(e.currentTarget);

            if (key === 'Enter') {
                if (!this.$el.find('[data-action="addPhoneNumber"]').hasClass('disabled')) {
                    this.addPhoneNumber();

                    e.stopPropagation();
                }

                return;
            }

            if (key === 'Backspace' && $target.val() === '') {
                let $block = $target.closest('div.phone-number-block');

                this._itemJustRemoved = true;
                setTimeout(() => this._itemJustRemoved = false, 100);

                e.stopPropagation();

                this.removePhoneNumber($block);

                setTimeout(() => this.focusOnLast(true), 50);
            }
        },
    }

    validateRequired() {
        if (!this.isRequired()) {
            return;
        }

        if (!this.model.get(this.name)) {
            let msg = this.translate('fieldIsRequired', 'messages')
                .replace('{field}', this.getLabelText());

            this.showValidationMessage(msg, 'div.phone-number-block:nth-child(1) input.phone-number');

            return true;
        }
    }

    // noinspection JSUnusedGlobalSymbols
    validatePhoneData() {
        let data = this.model.get(this.dataFieldName);

        if (!data || !data.length) {
            return;
        }

        /** @var {string} */
        let pattern = '^' + this.getMetadata().get(['app', 'regExpPatterns', 'phoneNumberLoose', 'pattern']) + '$';
        let regExp = new RegExp(pattern);

        let numberList = [];
        let notValid = false;

        data.forEach((row, i) => {
            let number = row.phoneNumber;

            if (!regExp.test(number)) {
                notValid = true;

                let msg = this.translate('fieldPhoneInvalidCharacters', 'messages')
                    .replace('{field}', this.getLabelText());

                this.showValidationMessage(msg, 'div.phone-number-block:nth-child(' + (i + 1)
                    .toString() + ') input.phone-number');
            }

            let numberClean = String(number).replace(/[\s+]/g, '');

            if (~numberList.indexOf(numberClean)) {
                let msg = this.translate('fieldValueDuplicate', 'messages')
                    .replace('{field}', this.getLabelText());

                this.showValidationMessage(msg, 'div.phone-number-block:nth-child(' + (i + 1)
                    .toString() + ') input.phone-number');

                notValid = true;

                return;
            }

            numberList.push(numberClean);
        });

        if (notValid) {
            return true;
        }
    }

    data() {
        let phoneNumberData;

        if (this.mode === this.MODE_EDIT) {
            phoneNumberData = Espo.Utils.cloneDeep(this.model.get(this.dataFieldName));

            if (this.model.isNew() || !this.model.get(this.name)) {
                if (!phoneNumberData || !phoneNumberData.length) {
                    let optOut;

                    if (this.model.isNew()) {
                        optOut = this.phoneNumberOptedOutByDefault && this.model.entityType !== 'User';
                    } else {
                        optOut = this.model.get(this.isOptedOutFieldName)
                    }

                    phoneNumberData = [{
                        phoneNumber: this.model.get(this.name) || '',
                        primary: true,
                        type: this.defaultType,
                        optOut: optOut,
                        invalid: false,
                    }];
                }
            }
        } else {
            phoneNumberData = this.model.get(this.dataFieldName) || false;
        }

        if (phoneNumberData) {
            phoneNumberData = Espo.Utils.cloneDeep(phoneNumberData);

            phoneNumberData.forEach((item) => {
                let number = item.phoneNumber || '';

                item.erased = number.indexOf(this.erasedPlaceholder) === 0;

                if (!item.erased) {
                    item.valueForLink = number.replace(/ /g, '');
                }

                item.lineThrough = item.optOut || item.invalid || this.model.get('doNotCall');
            });
        }

        if ((!phoneNumberData || phoneNumberData.length === 0) && this.model.get(this.name)) {
            let number = this.model.get(this.name);

            let o = {
                phoneNumber: number,
                primary: true,
                valueForLink: number.replace(/ /g, ''),
            };

            if (this.mode === 'edit' && this.model.isNew()) {
                o.type = this.defaultType;
            }

            phoneNumberData = [o];
        }

        let data = {
            ...super.data(),
            phoneNumberData: phoneNumberData,
            doNotCall: this.model.get('doNotCall'),
            lineThrough: this.model.get('doNotCall') || this.model.get(this.isOptedOutFieldName),
        };

        if (this.isReadMode()) {
            data.isOptedOut = this.model.get(this.isOptedOutFieldName);
            data.isInvalid = this.model.get(this.isInvalidFieldName);

            if (this.model.get(this.name)) {
                data.isErased = this.model.get(this.name).indexOf(this.erasedPlaceholder) === 0;

                if (!data.isErased) {
                    data.valueForLink = this.model.get(this.name).replace(/ /g, '');
                }
            }

            data.valueIsSet = this.model.has(this.name);
        }

        data.itemMaxLength = this.itemMaxLength;

        return data;
    }

    focusOnLast(cursorAtEnd) {
        let $item = this.$el.find('input.form-control').last();

        $item.focus();

        if (cursorAtEnd && $item[0]) {
            $item[0].setSelectionRange($item[0].value.length, $item[0].value.length);
        }
    }

    removePhoneNumber($block) {
        if ($block.parent().children().length === 1) {
            $block.find('input.phone-number').val('');
        } else {
            this.removePhoneNumberBlock($block);
        }

        this.trigger('change');
    }

    addPhoneNumber() {
        let data = Espo.Utils.cloneDeep(this.fetchPhoneNumberData());

        let o = {
            phoneNumber: '',
            primary: !data.length,
            type: false,
            optOut: this.emailAddressOptedOutByDefault,
            invalid: false,
        };

        data.push(o);

        this.model.set(this.dataFieldName, data, {silent: true});

        this.reRender()
            .then(() => this.focusOnLast());
    }

    afterRender() {
        super.afterRender();

        this.manageButtonsVisibility();
        this.manageAddButton();

        if (this.mode === this.MODE_EDIT) {
            this.$el.find('select').toArray().forEach(selectElement => {
                Select.init($(selectElement));
            });
        }
    }

    removePhoneNumberBlock($block) {
        let changePrimary = false;

        if ($block.find('button[data-property-type="primary"]').hasClass('active')) {
            changePrimary = true;
        }

        $block.remove();

        if (changePrimary) {
            this.$el.find('button[data-property-type="primary"]')
                .first()
                .addClass('active')
                .children()
                .removeClass('text-muted');
        }

        this.manageButtonsVisibility();
        this.manageAddButton();
    }

    manageAddButton() {
        let $input = this.$el.find('input.phone-number');
        let c = 0;

        $input.each((i, input) => {
            if (input.value !== '') {
                c++;
            }
        });

        if (c === $input.length) {
            this.$el.find('[data-action="addPhoneNumber"]')
                .removeClass('disabled')
                .removeAttr('disabled');

            return;
        }

        this.$el.find('[data-action="addPhoneNumber"]')
            .addClass('disabled')
            .attr('disabled', 'disabled');
    }

    manageButtonsVisibility() {
        let $primary = this.$el.find('button[data-property-type="primary"]');
        let $remove = this.$el.find('button[data-action="removePhoneNumber"]');
        let $container = this.$el.find('.phone-number-block-container');

        if ($primary.length > 1) {
            $primary.removeClass('hidden');
            $remove.removeClass('hidden');
            $container.addClass('many')

            return;
        }

        $container.removeClass('many')
        $primary.addClass('hidden');
        $remove.addClass('hidden');
    }

    setup() {
        this.dataFieldName = this.name + 'Data';
        this.defaultType = this.defaultType ||
            this.getMetadata()
                .get('entityDefs.' + this.model.entityType + '.fields.' + this.name + '.defaultType');

        this.isOptedOutFieldName = this.name + 'IsOptedOut';
        this.isInvalidFieldName = this.name + 'IsInvalid';

        this.phoneNumberOptedOutByDefault = this.getConfig().get('phoneNumberIsOptedOutByDefault');

        if (this.model.has('doNotCall')) {
            this.listenTo(this.model, 'change:doNotCall', (model, value, o) => {
                if (this.mode !== 'detail' && this.mode !== 'list') {
                    return;
                }

                if (!o.ui) {
                    return;
                }

                this.reRender();
            });
        }

        this.erasedPlaceholder = 'ERASED:';

        this.itemMaxLength = this.getMetadata()
            .get(['entityDefs', 'PhoneNumber', 'fields', 'name', 'maxLength']);
    }

    fetchPhoneNumberData() {
        let data = [];

        let $list = this.$el.find('div.phone-number-block');

        if ($list.length) {
            $list.each((i, d) => {
                let row = {};
                let $d = $(d);

                row.phoneNumber = $d.find('input.phone-number').val().trim();

                if (row.phoneNumber === '') {
                    return;
                }

                row.primary = $d.find('button[data-property-type="primary"]').hasClass('active');
                row.type = $d.find('select[data-property-type="type"]').val();
                row.optOut = $d.find('button[data-property-type="optOut"]').hasClass('active');
                row.invalid = $d.find('button[data-property-type="invalid"]').hasClass('active');

                data.push(row);
            });
        }

        return data;
    }

    fetch() {
        let data = {};

        let addressData = this.fetchPhoneNumberData() || [];

        data[this.dataFieldName] = addressData;
        data[this.name] = null;
        data[this.isOptedOutFieldName] = false;
        data[this.isInvalidFieldName] = false;

        let primaryIndex = 0;

        addressData.forEach((item, i) => {
            if (item.primary) {
                primaryIndex = i;

                if (item.optOut) {
                    data[this.isOptedOutFieldName] = true;
                }

                if (item.invalid) {
                    data[this.isInvalidFieldName] = true;
                }
            }
        });

        if (addressData.length && primaryIndex > 0) {
            let t = addressData[0];

            addressData[0] = addressData[primaryIndex];
            addressData[primaryIndex] = t;
        }

        if (addressData.length) {
            data[this.name] = addressData[0].phoneNumber;
        } else {
            data[this.isOptedOutFieldName] = null;
            data[this.isInvalidFieldName] = null;
        }

        return data;
    }
}

export default PhoneFieldView;
PK]�X�]]views/fields/foreign-url.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import UrlFieldView from 'views/fields/url';
import Helper from 'helpers/misc/foreign-field';

class ForeignUrlFieldView extends UrlFieldView {

    type = 'foreign'
    readOnly = true

    setup() {
        super.setup();

        const helper = new Helper(this);

        const foreignParams = helper.getForeignParams();

        for (let param in foreignParams) {
            this.params[param] = foreignParams[param];
        }
    }
}

export default ForeignUrlFieldView;
PK]��GFFviews/fields/teams.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import LinkMultipleFieldView from 'views/fields/link-multiple';

class TeamsFieldView extends LinkMultipleFieldView {

    init() {
        this.assignmentPermission = this.getAcl().getPermissionLevel('assignmentPermission');

        super.init();
    }

    getSelectBoolFilterList() {
        if (this.assignmentPermission === 'team' || this.assignmentPermission === 'no') {
            return ['onlyMy'];
        }
    }
}

export default TeamsFieldView;
PK]t��>�>views/fields/enum.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/fields/enumeration */

import BaseFieldView from 'views/fields/base';
import MultiSelect from 'ui/multi-select';
import Select from 'ui/select'

/**
 * An enum field (select-box).
 */
class EnumFieldView extends BaseFieldView {

    /**
     * @typedef {Object} module:views/fields/enumeration~options
     * @property {
     *     module:views/fields/enumeration~params &
     *     module:views/fields/base~params &
     *     Object.<string, *>
     * } [params] Parameters.
     */

    /**
     * @typedef {Object} module:views/fields/enumeration~params
     * @property {string[]} [options] Select options.
     * @property {boolean} [required] Required.
     * @property {string} [translation] A translation string. E.g. `Global.scopeNames`.
     * @property {boolean} [displayAsLabel] Display as label.
     * @property {string} [optionsReference] A reference to options. E.g. `Account.industry`.
     * @property {string} [optionsPath] An options metadata path.
     * @property {boolean} [isSorted] To sort options.
     * @property {Object.<string, 'warning'|'danger'|'success'|'info'|'primary'>} [style] A style map.
     * @property {Object.<string, string>} [translatedOptions] Option translations.
     */

    /**
     * @param {
     *     module:views/fields/enumeration~options &
     *     module:views/fields/base~options
     * } options Options.
     */
    constructor(options) {
        super(options);
    }

    type = 'enum'

    listTemplate = 'fields/enum/list'
    listLinkTemplate = 'fields/enum/list-link'
    detailTemplate = 'fields/enum/detail'
    editTemplate = 'fields/enum/edit'
    searchTemplate = 'fields/enum/search'

    translatedOptions = null

    /**
     * @todo Remove? Always treat as true.
     */
    fetchEmptyValueAsNull = true

    searchTypeList = [
        'anyOf',
        'noneOf',
        'isEmpty',
        'isNotEmpty',
    ]

    validationElementSelector = '.selectize-control'

    /** @inheritDoc */
    data() {
        let data = super.data();

        data.translatedOptions = this.translatedOptions;

        let value = this.model.get(this.name);

        if (this.isReadMode() && this.styleMap) {
            data.style = this.styleMap[value || ''] || 'default';
        }

        if (this.isReadMode()) {
            if (this.params.displayAsLabel && data.style && data.style !== 'default') {
                data.class = 'label label-md label';
            } else {
                data.class = 'text';
            }
        }

        let translationKey = value || '';

        if (
            typeof value !== 'undefined' && value !== null && value !== ''
            ||
            translationKey === '' && (
                translationKey in (this.translatedOptions || {}) &&
                (this.translatedOptions || {})[translationKey] !== ''
            )
        ) {
            data.isNotEmpty = true;
        }

        data.valueIsSet = this.model.has(this.name);

        if (data.isNotEmpty) {
            data.valueTranslated =
                this.translatedOptions ?
                    (this.translatedOptions[translationKey] || value) :
                    this.getLanguage().translateOption(translationKey, this.name, this.entityType);

        }

        return data;
    }

    setup() {
        if (!this.params.options) {
            let methodName = 'get' + Espo.Utils.upperCaseFirst(this.name) + 'Options';

            if (typeof this.model[methodName] === 'function') {
                this.params.options = this.model[methodName].call(this.model);
            }
        }

        let optionsPath = this.params.optionsPath;
        /** @type {?string} */
        let optionsReference = this.params.optionsReference;

        if (!optionsPath && optionsReference) {
            let [refEntityType, refField] = optionsReference.split('.');

            optionsPath = `entityDefs.${refEntityType}.fields.${refField}.options`;
        }

        if (optionsPath) {
            this.params.options = Espo.Utils.clone(this.getMetadata().get(optionsPath)) || [];
        }

        this.styleMap = this.params.style || this.model.getFieldParam(this.name, 'style') || {};

        this.setupOptions();

        if ('translatedOptions' in this.options) {
            this.translatedOptions = this.options.translatedOptions;
        }

        if ('translatedOptions' in this.params) {
            this.translatedOptions = this.params.translatedOptions;
        }

        this.setupTranslation();

        if (this.translatedOptions === null) {
            this.translatedOptions = this.getLanguage()
                .translate(this.name, 'options', this.model.name) || {};

            if (this.translatedOptions === this.name) {
                this.translatedOptions = null;
            }
        }

        if (this.params.isSorted && this.translatedOptions) {
            this.params.options = Espo.Utils.clone(this.params.options) || [];

            this.params.options = this.params.options.sort((v1, v2) => {
                 return (this.translatedOptions[v1] || v1)
                     .localeCompare(this.translatedOptions[v2] || v2);
            });
        }

        if (this.options.customOptionList) {
            this.setOptionList(this.options.customOptionList);
        }
    }

    setupTranslation() {
        let translation = this.params.translation;
        /** @type {?string} */
        let optionsReference = this.params.optionsReference;

        if (!translation && optionsReference) {
            let [refEntityType, refField] = optionsReference.split('.');

            translation = `${refEntityType}.options.${refField}`;
        }

        if (!translation) {
            return;
        }

        this.translatedOptions = null;

        if (!this.params.options) {
            return;
        }

        let obj = this.getLanguage().translatePath(translation);

        let map = {};

        this.params.options.forEach(item => {
            if (typeof obj === 'object' && item in obj) {
                map[item] = obj[item];

                return;
            }

            if (
                Array.isArray(obj) &&
                typeof item === 'number' &&
                typeof obj[item] !== 'undefined'
            ) {
                map[item.toString()] = obj[item];

                return;
            }

            map[item] = item;
        });

        let value = this.model.get(this.name);

        if ((value || value === '') && !(value in map)) {
            if (typeof obj === 'object' && value in obj) {
                map[value] = obj[value];
            }
        }

        this.translatedOptions = map;
    }

    /**
     * Set up options.
     */
    setupOptions() {}

    /**
     * Set an option list.
     *
     * @param {string[]} optionList An option list.
     */
    setOptionList(optionList) {
        let previousOptions = this.params.options;

        if (!this.originalOptionList) {
            this.originalOptionList = this.params.options;
        }

        let newOptions = Espo.Utils.clone(optionList) || [];

        this.params.options = newOptions;

        let isChanged = !_(previousOptions).isEqual(optionList);

        if (!this.isEditMode() || !isChanged) {
            return;
        }

        let triggerChange = false;
        let currentValue = this.model.get(this.name);

        if (!newOptions.includes(currentValue) && this.isReady) {
            this.model.set(this.name, newOptions[0] ?? null, {silent: true});

            triggerChange = true;
        }

        this.reRender()
            .then(() => {
                if (triggerChange) {
                    this.trigger('change');
                }
            });
    }

    /**
     * Reset a previously set option list.
     */
    resetOptionList() {
        if (!this.originalOptionList) {
            return;
        }

        let previousOptions = this.params.options;

        this.params.options = Espo.Utils.clone(this.originalOptionList);

        let isChanged = !_(previousOptions).isEqual(this.originalOptionList);

        if (!this.isEditMode() || !isChanged) {
            return;
        }

        if (this.isRendered()) {
            this.reRender();
        }
    }

    setupSearch() {
        this.events = _.extend({
            'change select.search-type': (e) => {
                this.handleSearchType($(e.currentTarget).val());
            },
        }, this.events || {});
    }

    handleSearchType(type) {
        var $inputContainer = this.$el.find('div.input-container');

        if (~['anyOf', 'noneOf'].indexOf(type)) {
            $inputContainer.removeClass('hidden');
        } else {
            $inputContainer.addClass('hidden');
        }
    }

    afterRender() {
        super.afterRender();

        if (this.isSearchMode()) {
            this.$element = this.$el.find('.main-element');

            let type = this.$el.find('select.search-type').val();

            this.handleSearchType(type);

            let valueList = this.getSearchParamsData().valueList || this.searchParams.value || [];

            this.$element.val(valueList.join(':,:'));

            let items = [];

            (this.params.options || []).forEach(value => {
                let label = this.getLanguage().translateOption(value, this.name, this.scope);

                if (this.translatedOptions) {
                    if (value in this.translatedOptions) {
                        label = this.translatedOptions[value];
                    }
                }

                if (label === '') {
                    return;
                }

                items.push({
                    value: value,
                    text: label,
                });
            });

            /** @type {module:ui/multi-select~Options} */
            let multiSelectOptions = {
                items: items,
                delimiter: ':,:',
                matchAnyWord: true,
            };

            MultiSelect.init(this.$element, multiSelectOptions);

            this.$el.find('.selectize-dropdown-content').addClass('small');
            this.$el.find('select.search-type').on('change', () => this.trigger('change'));
            this.$element.on('change', () => this.trigger('change'));
        }

        if (this.isEditMode() || this.isSearchMode()) {
            Select.init(this.$element, {matchAnyWord: true});
        }
    }

    focusOnInlineEdit() {
        Select.focus(this.$element);
    }

    validateRequired() {
        if (this.isRequired()) {
            if (!this.model.get(this.name)) {
                let msg = this.translate('fieldIsRequired', 'messages')
                    .replace('{field}', this.getLabelText());

                this.showValidationMessage(msg);

                return true;
            }
        }
    }

    fetch() {
        let value = this.$element.val();

        if (this.fetchEmptyValueAsNull && !value) {
            value = null;
        }

        let data = {};

        data[this.name] = value;

        return data;
    }

    parseItemForSearch(item) {
        return item;
    }

    fetchSearch() {
        let type = this.fetchSearchType();

        let list = this.$element.val().split(':,:');

        if (list.length === 1 && list[0] === '') {
            list = [];
        }

        list.forEach((item, i) => {
            list[i] = this.parseItemForSearch(item);
        });

        if (type === 'anyOf') {
            if (list.length === 0) {
                return {
                    type: 'any',
                    data: {
                        type: 'anyOf',
                        valueList: list,
                    },
                };
            }

            return {
                type: 'in',
                value: list,
                data: {
                    type: 'anyOf',
                    valueList: list,
                },
            };
        }

        if (type === 'noneOf') {
            if (list.length === 0) {
                return {
                    type: 'any',
                    data: {
                        type: 'noneOf',
                        valueList: list,
                    },
                };
            }

            return {
                type: 'or',
                value: [
                    // Don't change order.
                    {
                        type: 'notIn',
                        value: list,
                        attribute: this.name,
                    },
                    {
                        type: 'isNull',
                        attribute: this.name,
                    },
                ],
                data: {
                    type: 'noneOf',
                    valueList: list,
                },
            };
        }

        if (type === 'isEmpty') {
            return {
                type: 'or',
                value: [
                    {
                        type: 'isNull',
                        attribute: this.name,
                    },
                    {
                        type: 'equals',
                        value: '',
                        attribute: this.name,
                    }
                ],
                data: {
                    type: 'isEmpty',
                },
            };
        }

        if (type === 'isNotEmpty') {
            let value = [
                {
                    type: 'isNotNull',
                    attribute: this.name,
                },
            ];

            if (!this.model.getFieldParam(this.name, 'notStorable')) {
                value.push({
                    type: 'notEquals',
                    value: '',
                    attribute: this.name,
                });
            }

            return {
                type: 'and',
                value: value,
                data: {
                    type: 'isNotEmpty',
                },
            };
        }

        return null;
    }

    getSearchType() {
        return this.getSearchParamsData().type || 'anyOf';
    }
}

export default EnumFieldView;
PK]�����
�
views/fields/url-multiple.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ArrayFieldView from 'views/fields/array';

/**
 * An Url-Multiple field.
 */
class UrlMultipleFieldView extends ArrayFieldView {

    type = 'urlMultiple'

    maxItemLength = 255
    displayAsList = true
    defaultProtocol = 'https:'

    setup() {
        super.setup();

        this.noEmptyString = true;
        this.params.pattern = '$uriOptionalProtocol';
    }

    addValueFromUi(value) {
        value = value.trim();

        if (this.params.strip) {
            value = this.strip(value);
        }

        if (value === decodeURI(value)) {
            value = encodeURI(value);
        }

        super.addValueFromUi(value);
    }

    /**
     * @param {string} value
     * @return {string}
     */
    strip(value) {
        if (value.indexOf('//') !== -1) {
            value = value.substring(value.indexOf('//') + 2);
        }

        value = value.replace(/\/+$/, '');

        return value;
    }

    prepareUrl(url) {
        if (url.indexOf('//') === -1) {
            url = this.defaultProtocol + '//' + url;
        }

        return url;
    }

    getValueForDisplay() {
        /** @type {JQuery[]} */
        let $list = this.selected.map(value => {
            return $('<a>')
                .attr('href', this.prepareUrl(value))
                .attr('target', '_blank')
                .text(decodeURI(value));
        });

        return $list
            .map($item =>
                $('<div>')
                    .addClass('multi-enum-item-container')
                    .append($item)
                    .get(0).outerHTML
            )
            .join('');
    }

    getItemHtml(value) {
        let html = super.getItemHtml(value);

        let $item = $(html);

        $item.find('span.text').html(
            $('<a>')
                .attr('href', this.prepareUrl(value))
                .css('user-drag', 'none')
                .attr('target', '_blank')
                .text(decodeURI(value))
        );

        return $item.get(0).outerHTML;
    }
}

export default UrlMultipleFieldView;
PK]���G��views/fields/address-city.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import VarcharFieldView from 'views/fields/varchar';

class AddressCityFieldView extends VarcharFieldView {

    setupOptions() {
        let cityList = this.getConfig().get('addressCityList') || [];

        if (cityList.length) {
            this.params.options = Espo.Utils.clone(cityList);
        }
    }
}

export default AddressCityFieldView;
PK]!h�hPhPviews/fields/link-parent.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/fields/link-parent */

import BaseFieldView from 'views/fields/base';
import RecordModal from 'helpers/record-modal';
import Select from 'ui/select';

/**
 * A link-parent field (belongs-to-parent relation).
 */
class LinkParentFieldView extends BaseFieldView {

    type = 'linkParent'

    listTemplate = 'fields/link-parent/list'
    detailTemplate = 'fields/link-parent/detail'
    editTemplate = 'fields/link-parent/edit'
    searchTemplate = 'fields/link-parent/search'
    listLinkTemplate = 'fields/link-parent/list-link'

    /**
     * A name attribute name.
     *
     * @type {string}
     */
    nameName = null

    /**
     * An ID attribute name.
     *
     * @type {string}
     */
    idName = null

    /**
     * A type attribute name.
     *
     * @type {string}
     */
    typeName = null

    /**
     * A current foreign entity type.
     *
     * @type {string|null}
     */
    foreignScope = null

    /**
     * A foreign entity type list.
     *
     * @type {string[]}
     */
    foreignScopeList = null

    /**
     * Autocomplete disabled.
     *
     * @protected
     * @type {boolean}
     */
    autocompleteDisabled = false

    /**
     * A select-record view.
     *
     * @protected
     * @type {string}
     */
    selectRecordsView = 'views/modals/select-records'

    /**
     * Create disabled.
     *
     * @protected
     * @type {boolean}
     */
    createDisabled = false

    /**
     * A search type list.
     *
     * @protected
     * @type {string[]}
     */
    searchTypeList = [
        'is',
        'isEmpty',
        'isNotEmpty',
    ]

    /**
     * A select primary filter.
     *
     * @protected
     * @type {string|null}
     */
    selectPrimaryFilterName = null

    /**
     * A select bool filter list.
     *
     * @protected
     * @type {string[]|null}
     */
    selectBoolFilterList = null

    /**
     * An autocomplete max record number.
     *
     * @protected
     * @type {number|null}
     */
    autocompleteMaxCount = null

    /**
     * Select all attributes.
     *
     * @protected
     * @type {boolean}
     */
    forceSelectAllAttributes = false

    /**
     * Mandatory select attributes.
     *
     * @protected
     * @type {string[]|null}
     */
    mandatorySelectAttributeList = null

    /** @inheritDoc */
    initialSearchIsNotIdle = true

    /** @inheritDoc */
    events = {
        /** @this LinkParentFieldView */
        'auxclick a[href]:not([role="button"])': function (e) {
            if (!this.isReadMode()) {
                return;
            }

            let isCombination = e.button === 1 && (e.ctrlKey || e.metaKey);

            if (!isCombination) {
                return;
            }

            e.preventDefault();
            e.stopPropagation();

            this.quickView();
        },
    }

    data() {
        let nameValue = this.model.get(this.nameName);

        if (!nameValue && this.model.get(this.idName) && this.model.get(this.typeName)) {
            nameValue = this.translate(this.model.get(this.typeName), 'scopeNames');
        }

        let iconHtml = null;

        if (
            (
                this.mode === this.MODE_DETAIL ||
                this.mode === this.MODE_LIST && this.displayScopeColorInListMode
            ) &&
            this.foreignScope
        ) {
            iconHtml = this.getHelper().getScopeColorIconHtml(this.foreignScope);
        }

        return {
            ...super.data(),
            idName: this.idName,
            nameName: this.nameName,
            typeName: this.typeName,
            idValue: this.model.get(this.idName),
            nameValue: nameValue,
            typeValue: this.model.get(this.typeName),
            foreignScope: this.foreignScope,
            foreignScopeList: this.foreignScopeList,
            valueIsSet: this.model.has(this.idName) || this.model.has(this.typeName),
            iconHtml: iconHtml,
            displayEntityType: this.displayEntityType && this.model.get(this.typeName),
        };
    }

    /**
     * Get advanced filters (field filters) to be applied when select a record.
     * Can be extended.
     *
     * @protected
     * @return {Object.<string,module:search-manager~advancedFilter>|null}
     */
    getSelectFilters() {
        return null;
    }

    /**
     * Get a select bool filter list. Applied when select a record.
     * Can be extended.
     *
     * @protected
     * @return {string[]|null}
     */
    getSelectBoolFilterList() {
        return this.selectBoolFilterList;
    }

    /**
     * Get a select primary filter. Applied when select a record.
     * Can be extended.
     *
     * @protected
     * @return {string|null}
     */
    getSelectPrimaryFilterName() {
        return this.selectPrimaryFilterName;
    }

    /**
     * Attributes to pass to a model when creating a new record.
     * Can be extended.
     *
     * @return {Object.<string,*>|null}
     */
    getCreateAttributes() {
        return null;
    }

    /** @inheritDoc */
    setup() {
        this.nameName = this.name + 'Name';
        this.typeName = this.name + 'Type';
        this.idName = this.name + 'Id';

        this.foreignScopeList = this.options.foreignScopeList || this.foreignScopeList;

        this.foreignScopeList = this.foreignScopeList ||
            this.params.entityList ||
            this.model.getLinkParam(this.name, 'entityList') || [];

        this.foreignScopeList = Espo.Utils.clone(this.foreignScopeList).filter(item => {
            if (!this.getMetadata().get(['scopes', item, 'disabled'])) {
                return true;
            }
        });

        this.foreignScope = this.model.get(this.typeName) || this.foreignScopeList[0];

        if (this.foreignScope && !~this.foreignScopeList.indexOf(this.foreignScope)) {
            this.foreignScopeList.unshift(this.foreignScope);
        }

        this.listenTo(this.model, 'change:' + this.typeName, () => {
            this.foreignScope = this.model.get(this.typeName) || this.foreignScopeList[0];
        });

        if ('createDisabled' in this.options) {
            this.createDisabled = this.options.createDisabled;
        }

        if (!this.isListMode()) {
            this.addActionHandler('selectLink', () => {
                Espo.Ui.notify(' ... ');

                let viewName = this.getMetadata()
                        .get('clientDefs.' + this.foreignScope + '.modalViews.select') ||
                    this.selectRecordsView;

                let createButton = !this.createDisabled && this.isEditMode();

                this.createView('dialog', viewName, {
                    scope: this.foreignScope,
                    createButton: createButton,
                    filters: this.getSelectFilters(),
                    boolFilterList: this.getSelectBoolFilterList(),
                    primaryFilterName: this.getSelectPrimaryFilterName(),
                    createAttributes: createButton ? this.getCreateAttributes() : null,
                    mandatorySelectAttributeList: this.getMandatorySelectAttributeList(),
                    forceSelectAllAttributes: this.isForceSelectAllAttributes(),
                }, dialog => {
                    dialog.render();

                    Espo.Ui.notify(false);

                    this.listenToOnce(dialog, 'select', (model) => {
                        this.clearView('dialog');
                        this.select(model);
                    });
                });
            });

            this.addActionHandler('clearLink', () => {
                if (this.foreignScopeList.length) {
                    this.foreignScope = this.foreignScopeList[0];
                    Select.setValue(this.$elementType, this.foreignScope);
                }

                this.$elementName.val('');
                this.$elementId.val('');

                this.trigger('change');
            });

            this.events['change select[data-name="'+this.typeName+'"]'] = (e) => {
                this.foreignScope = e.currentTarget.value;
                this.$elementName.val('');
                this.$elementId.val('');
            };
        }
    }

    /** @inheritDoc */
    setupSearch() {
        let type = this.getSearchParamsData().type;

        if (type === 'is' || !type) {
            this.searchData.idValue = this.getSearchParamsData().idValue ||
                this.searchParams.valueId;
            this.searchData.nameValue = this.getSearchParamsData().nameValue ||
                this.searchParams.valueName;
            this.searchData.typeValue = this.getSearchParamsData().typeValue ||
                this.searchParams.valueType;
        }

        this.events['change select.search-type'] = e => {
            let type = $(e.currentTarget).val();

            this.handleSearchType(type);
        };
    }

    /**
     * Handle a search type.
     *
     * @protected
     * @param {string} type A type.
     */
    handleSearchType(type) {
        if (~['is'].indexOf(type)) {
            this.$el.find('div.primary').removeClass('hidden');
        } else {
            this.$el.find('div.primary').addClass('hidden');
        }
    }

    /**
     * Select.
     *
     * @param {module:model} model A model.
     * @protected
     */
    select(model) {
        this.$elementName.val(model.get('name') || model.id);
        this.$elementId.val(model.get('id'));

        this.trigger('change');
    }

    /**
     * Attributes to select regardless availability on a list layout.
     * Can be extended.
     *
     * @protected
     * @return {string[]|null}
     */
    getMandatorySelectAttributeList() {
        return this.mandatorySelectAttributeList;
    }

    /**
     * Select all attributes. Can be extended.
     *
     * @protected
     * @return {boolean}
     */
    isForceSelectAllAttributes() {
        return this.forceSelectAllAttributes;
    }

    /**
     * Get an autocomplete max record number. Can be extended.
     *
     * @protected
     * @return {number}
     */
    getAutocompleteMaxCount() {
        if (this.autocompleteMaxCount) {
            return this.autocompleteMaxCount;
        }

        return this.getConfig().get('recordsPerPage');
    }

    /**
     * Compose an autocomplete URL. Can be extended.
     *
     * @protected
     * @return {string}
     */
    getAutocompleteUrl() {
        let url = this.foreignScope + '?maxSize=' + this.getAutocompleteMaxCount();

        if (!this.isForceSelectAllAttributes()) {
            let select = ['id', 'name'];

            if (this.getMandatorySelectAttributeList()) {
                select = select.concat(this.getMandatorySelectAttributeList());
            }

            url += '&select=' + select.join(',');
        }

        let boolList = this.getSelectBoolFilterList();

        if (boolList) {
            url += '&' + $.param({'boolFilterList': boolList});
        }

        let primary = this.getSelectPrimaryFilterName();

        if (primary) {
            url += '&' + $.param({'primaryFilter': primary});
        }

        return url;
    }

    afterRender() {
        if (this.isEditMode() || this.isSearchMode()) {
            this.$elementId = this.$el.find('input[data-name="' + this.idName + '"]');
            this.$elementName = this.$el.find('input[data-name="' + this.nameName + '"]');
            this.$elementType = this.$el.find('select[data-name="' + this.typeName + '"]');

            this.$elementName.on('change', () => {
                if (this.$elementName.val() === '') {
                    this.$elementName.val('');
                    this.$elementId.val('');

                    this.trigger('change');
                }
            });

            this.$elementType.on('change', () => {
                this.$elementName.val('');
                this.$elementId.val('');

                this.trigger('change');
            });

            this.$elementName.on('blur', e => {
                setTimeout(() => {
                    if (this.mode === this.MODE_EDIT) {
                        e.currentTarget.value = this.model.get(this.nameName) || '';
                    }
                }, 100);

                if (!this.autocompleteDisabled) {
                    setTimeout(() => this.$elementName.autocomplete('clear'), 300);
                }
            });

            if (!this.autocompleteDisabled) {
                this.$elementName.autocomplete({
                    serviceUrl: (q) => {
                        return this.getAutocompleteUrl(q);
                    },
                    minChars: 1,
                    paramName: 'q',
                    noCache: true,
                    triggerSelectOnValidInput: false,
                    autoSelectFirst: true,
                    beforeRender: ($c) => {
                        if (this.$elementName.hasClass('input-sm')) {
                            $c.addClass('small');
                        }
                    },
                    formatResult: (suggestion) => {
                        return this.getHelper().escapeString(suggestion.name);
                    },
                    transformResult: (response) => {
                        response = JSON.parse(response);
                        let list = [];

                        response.list.forEach(item => {
                            list.push({
                                id: item.id,
                                name: item.name || item.id,
                                data: item.id,
                                value: item.name || item.id,
                                attributes: item,
                            });
                        });

                        return {suggestions: list};
                    },
                    onSelect: (s) => {
                        this.getModelFactory().create(this.foreignScope, (model) => {
                            model.set(s.attributes);

                            this.select(model);
                            this.$elementName.focus();
                        });
                    },
                });

                this.$elementName.off('focus.autocomplete');
                this.$elementName.on('focus', () => this.$elementName.get(0).select());

                this.$elementName.attr('autocomplete', 'espo-' + this.name);

                Select.init(this.$elementType, {});
            }

            let $elementName = this.$elementName;

            this.once('render', () => {
                $elementName.autocomplete('dispose');
            });

            this.once('remove', () => {
                $elementName.autocomplete('dispose');
            });
        }

        if (this.mode === 'search') {
            let type = this.$el.find('select.search-type').val();

            this.handleSearchType(type);

            this.$el.find('select.search-type').on('change', () => {
                this.trigger('change');
            });
        }
    }

    /** @inheritDoc */
    getValueForDisplay() {
        return this.model.get(this.nameName);
    }

    /** @inheritDoc */
    validateRequired() {
        if (this.isRequired()) {
            if (this.model.get(this.idName) === null || !this.model.get(this.typeName)) {
                let msg = this.translate('fieldIsRequired', 'messages')
                    .replace('{field}', this.getLabelText());

                this.showValidationMessage(msg);

                return true;
            }
        }
    }

    /** @inheritDoc */
    fetch() {
        let data = {};

        data[this.typeName] = this.$elementType.val() || null;
        data[this.nameName] = this.$elementName.val() || null;
        data[this.idName] = this.$elementId.val() || null;

        if (data[this.idName] === null) {
            data[this.typeName] = null;
        }

        return data;
    }

    /** @inheritDoc */
    fetchSearch() {
        let type = this.$el.find('select.search-type').val();

        if (type === 'isEmpty') {
            return {
                type: 'isNull',
                field: this.idName,
                data: {
                    type: type,
                }
            };
        }

        if (type === 'isNotEmpty') {
            return {
                type: 'isNotNull',
                field: this.idName,
                data: {
                    type: type,
                }
            };
        }

        let entityType = this.$elementType.val();
        let entityName = this.$elementName.val()
        let entityId = this.$elementId.val();

        if (!entityType) {
            return null;
        }

        if (entityId) {
            return {
                type: 'and',
                attribute: this.idName,
                value: [
                    {
                        type: 'equals',
                        field: this.idName,
                        value: entityId,
                    },
                    {
                        type: 'equals',
                        field: this.typeName,
                        value: entityType,
                    }
                ],
                data: {
                    type: 'is',
                    idValue: entityId,
                    nameValue: entityName,
                    typeValue: entityType,
                }
            };
        }

        return {
            type: 'and',
            attribute: this.idName,
            value: [
                {
                    type: 'isNotNull',
                    field: this.idName,
                },
                {
                    type: 'equals',
                    field: this.typeName,
                    value: entityType,
                }
            ],
            data: {
                type: 'is',
                typeValue: entityType,
            }
        };
    }

    /** @inheritDoc */
    getSearchType() {
        return this.getSearchParamsData().type || this.searchParams.typeFront;
    }

    /**
     * @protected
     */
    quickView() {
        let id = this.model.get(this.idName);
        let entityType = this.model.get(this.typeName);

        if (!id || !entityType) {
            return;
        }

        let helper = new RecordModal(this.getMetadata(), this.getAcl());

        helper.showDetail(this, {
            id: id,
            scope: entityType,
        });
    }
}

export default LinkParentFieldView;
PK]�h����"views/fields/foreign-multi-enum.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import MultiEnumFieldView from 'views/fields/multi-enum';
import ForeignArrayFieldView from 'views/fields/foreign-array';

class ForeignMultiEnumFieldView extends MultiEnumFieldView {

    type = 'foreign'

    setupOptions() {
        ForeignArrayFieldView.prototype.setupOptions.call(this);
    }
}

export default ForeignMultiEnumFieldView;
PK]����views/fields/user.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import LinkFieldView from 'views/fields/link';

class UserFieldView extends LinkFieldView {

    searchTemplate = 'fields/user/search'

    setupSearch() {
        super.setupSearch();

        this.searchTypeList = Espo.Utils.clone(this.searchTypeList);
        this.searchTypeList.push('isFromTeams');

        this.searchData.teamIdList = this.getSearchParamsData().teamIdList ||
            this.searchParams.teamIdList || [];
        this.searchData.teamNameHash = this.getSearchParamsData().teamNameHash ||
            this.searchParams.teamNameHash || {};

        this.events['click a[data-action="clearLinkTeams"]'] = e => {
            let id = $(e.currentTarget).data('id').toString();

            this.deleteLinkTeams(id);
        };

        this.addActionHandler('selectLinkTeams', () => {
            Espo.Ui.notify(' ... ');

            let viewName = this.getMetadata().get('clientDefs.Team.modalViews.select') ||
                'views/modals/select-records';

            this.createView('dialog', viewName, {
                scope: 'Team',
                createButton: false,
                multiple: true,
            }, view => {
                view.render();

                Espo.Ui.notify(false);

                this.listenToOnce(view, 'select', models => {
                    if (Object.prototype.toString.call(models) !== '[object Array]') {
                        models = [models];
                    }

                    models.forEach(model => {
                        this.addLinkTeams(model.id, model.get('name'));
                    });
                });
            });
        });

        this.events['click a[data-action="clearLinkTeams"]'] = e => {
            let id = $(e.currentTarget).data('id').toString();

            this.deleteLinkTeams(id);
        };
    }

    handleSearchType(type) {
        super.handleSearchType(type);

        if (type === 'isFromTeams') {
            this.$el.find('div.teams-container').removeClass('hidden');
        }
        else {
            this.$el.find('div.teams-container').addClass('hidden');
        }
    }

    afterRender() {
        super.afterRender();

        if (this.mode === this.MODE_SEARCH) {
            let $elementTeams = this.$el.find('input.element-teams');


            $elementTeams.autocomplete({
                beforeRender: $c => {
                    if (this.$elementName.hasClass('input-sm')) {
                        $c.addClass('small');
                    }
                },
                serviceUrl: () => {
                    return 'Team?&maxSize=' + this.getAutocompleteMaxCount() + '&select=id,name';
                },
                minChars: 1,
                triggerSelectOnValidInput: false,
                paramName: 'q',
                noCache: true,
                formatResult: suggestion => {
                    // noinspection JSUnresolvedReference
                    return this.getHelper().escapeString(suggestion.name);
                },
                transformResult: response => {
                    response = JSON.parse(response);
                    let list = [];

                    response.list.forEach(item => {
                        list.push({
                            id: item.id,
                            name: item.name,
                            data: item.id,
                            value: item.name,
                        });
                    });

                    return {suggestions: list};
                },
                onSelect: /** {id: string, name: string } */s => {
                    this.addLinkTeams(s.id, s.name);

                    $elementTeams.val('');
                    $elementTeams.focus();
                },
            });

            $elementTeams.attr('autocomplete', 'espo-' + this.name);

            this.once('render', () => {
                $elementTeams.autocomplete('dispose');
            });

            this.once('remove', () => {
                $elementTeams.autocomplete('dispose');
            });

            let type = this.$el.find('select.search-type').val();

            if (type === 'isFromTeams') {
                this.searchData.teamIdList.forEach(id => {
                    this.addLinkTeamsHtml(id, this.searchData.teamNameHash[id]);
                });
            }
        }
    }

    deleteLinkTeams(id) {
        this.deleteLinkTeamsHtml(id);

        let index = this.searchData.teamIdList.indexOf(id);

        if (index > -1) {
            this.searchData.teamIdList.splice(index, 1);
        }

        delete this.searchData.teamNameHash[id];

        this.trigger('change');
    }

    addLinkTeams(id, name) {
        this.searchData.teamIdList = this.searchData.teamIdList || [];

        if (!~this.searchData.teamIdList.indexOf(id)) {
            this.searchData.teamIdList.push(id);
            this.searchData.teamNameHash[id] = name;
            this.addLinkTeamsHtml(id, name);

            this.trigger('change');
        }
    }

    deleteLinkTeamsHtml(id) {
        this.$el.find('.link-teams-container .link-' + id).remove();
    }

    addLinkTeamsHtml(id, name) {
        id = this.getHelper().escapeString(id);
        name = this.getHelper().escapeString(name);

        let $container = this.$el.find('.link-teams-container');

        let $el = $('<div />')
            .addClass('link-' + id)
            .addClass('list-group-item');

        $el.html(name + '&nbsp');

        $el.prepend(
            '<a role="button" class="pull-right" data-id="' + id + '" ' +
            'data-action="clearLinkTeams"><span class="fas fa-times"></a>'
        );

        $container.append($el);

        return $el;
    }

    fetchSearch() {
        let type = this.$el.find('select.search-type').val();

        if (type === 'isFromTeams') {
            return {
                type: 'isUserFromTeams',
                field: this.name,
                value: this.searchData.teamIdList,
                data: {
                    type: type,
                    teamIdList: this.searchData.teamIdList,
                    teamNameHash: this.searchData.teamNameHash,
                },
            };
        }

        return super.fetchSearch();
    }
}

export default UserFieldView;
PK]��`s`sviews/fields/link-multiple.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/fields/link-multiple */

import BaseFieldView from 'views/fields/base';
import RecordModal from 'helpers/record-modal';

/**
 * A link-multiple field (for has-many relations).
 */
class LinkMultipleFieldView extends BaseFieldView {

    type = 'linkMultiple'

    listTemplate = 'fields/link-multiple/list'
    detailTemplate = 'fields/link-multiple/detail'
    editTemplate = 'fields/link-multiple/edit'
    searchTemplate = 'fields/link-multiple/search'

    /**
     * A name-hash attribute name.
     *
     * @protected
     * @type {string}
     */
    nameHashName = null

    /**
     * A IDs attribute name.
     *
     * @protected
     * @type {string}
     */
    idsName = null

    /**
     * @protected
     * @type {Object.<string,string>|null}
     */
    nameHash = null

    /**
     * @protected
     * @type {string[]|null}
     */
    ids = null

    /**
     * A foreign entity type.
     *
     * @protected
     * @type {string}
     */
    foreignScope = null

    /**
     * Autocomplete disabled.
     *
     * @protected
     * @type {boolean}
     */
    autocompleteDisabled = false

    /**
     * A select-record view.
     *
     * @protected
     * @type {string}
     */
    selectRecordsView = 'views/modals/select-records'

    /**
     * Create disabled.
     *
     * @protected
     * @type {boolean}
     */
    createDisabled = false

    /**
     * Force create button even is disabled in clientDefs > relationshipPanels.
     *
     * @protected
     * @type {boolean}
     */
    forceCreateButton = false

    /**
     * @protected
     * @type {boolean}
     */
    sortable = false

    /**
     * A search type list.
     *
     * @protected
     * @type {string[]}
     */
    searchTypeList = [
        'anyOf',
        'isEmpty',
        'isNotEmpty',
        'noneOf',
        'allOf',
    ]

    /**
     * A primary filter list that will be available when selecting a record.
     *
     * @protected
     * @type {string[]|null}
     */
    selectFilterList = null

    /**
     * A select bool filter list.
     *
     * @protected
     * @type {string[]|null}
     */
    selectBoolFilterList = null

    /**
     * A select primary filter.
     *
     * @protected
     * @type {string|null}
     */
    selectPrimaryFilterName = null

    /**
     * An autocomplete max record number.
     *
     * @protected
     * @type {number|null}
     */
    autocompleteMaxCount = null

    /**
     * Trigger autocomplete on empty input.
     *
     * @protected
     * @type {boolean}
     */
    autocompleteOnEmpty = false

    /**
     * Select all attributes.
     *
     * @protected
     * @type {boolean}
     */
    forceSelectAllAttributes = false

    /**
     * @protected
     * @type {string}
     */
    iconHtml = ''

    /** @inheritDoc */
    events = {
        /** @this LinkMultipleFieldView */
        'auxclick a[href]:not([role="button"])': function (e) {
            if (!this.isReadMode()) {
                return;
            }

            let isCombination = e.button === 1 && (e.ctrlKey || e.metaKey);

            if (!isCombination) {
                return;
            }

            let id = $(e.currentTarget).attr('data-id');

            if (!id) {
                return;
            }

            e.preventDefault();
            e.stopPropagation();

            this.quickView(id);
        },
    }

    /** @inheritDoc */
    data() {
        let ids = this.model.get(this.idsName);

        return {
            ...super.data(),
            idValues: this.model.get(this.idsName),
            idValuesString: ids ? ids.join(',') : '',
            nameHash: this.model.get(this.nameHashName),
            foreignScope: this.foreignScope,
            valueIsSet: this.model.has(this.idsName),
        };
    }

    /**
     * Get advanced filters (field filters) to be applied when select a record.
     * Can be extended.
     *
     * @protected
     * @return {Object.<string, module:search-manager~advancedFilter>|null}
     */
    getSelectFilters() {
        return null;
    }

    /**
     * Get a select bool filter list. Applied when select a record.
     * Can be extended.
     *
     * @protected
     * @return {string[]|null}
     */
    getSelectBoolFilterList() {
        return this.selectBoolFilterList;
    }

    /**
     * Get a select primary filter. Applied when select a record.
     * Can be extended.
     *
     * @protected
     * @return {string|null}
     */
    getSelectPrimaryFilterName() {
        return this.selectPrimaryFilterName;
    }

    /**
     * Get a primary filter list that will be available when selecting a record.
     * Can be extended.
     *
     * @return {string[]|null}
     */
    getSelectFilterList() {
        return this.selectFilterList;
    }

    /**
     * Attributes to pass to a model when creating a new record.
     * Can be extended.
     *
     * @return {Object.<string, *>|null}
     */
    getCreateAttributes() {
        let attributeMap = this.getMetadata()
            .get(['clientDefs', this.entityType, 'relationshipPanels', this.name, 'createAttributeMap']) || {};

        let attributes = {};

        Object.keys(attributeMap).forEach(attr => attributes[attributeMap[attr]] = this.model.get(attr));

        return attributes;
    }

    /** @inheritDoc */
    setup() {
        this.nameHashName = this.name + 'Names';
        this.idsName = this.name + 'Ids';

        this.foreignScope = this.options.foreignScope ||
            this.foreignScope ||
            this.model.getFieldParam(this.name, 'entity') ||
            this.model.getLinkParam(this.name, 'entity');

        if ('createDisabled' in this.options) {
            this.createDisabled = this.options.createDisabled;
        }

        if (this.isSearchMode()) {
            let nameHash = this.getSearchParamsData().nameHash || this.searchParams.nameHash || {};
            let idList = this.getSearchParamsData().idList || this.searchParams.value || [];

            this.nameHash = Espo.Utils.clone(nameHash);
            this.ids = Espo.Utils.clone(idList);
        }
        else {
            this.copyValuesFromModel();
        }

        this.listenTo(this.model, 'change:' + this.idsName, () => {
            this.copyValuesFromModel();
        });

        this.sortable = this.sortable || this.params.sortable;

        this.iconHtml = this.getHelper().getScopeColorIconHtml(this.foreignScope);

        if (!this.isListMode()) {
            this.addActionHandler('selectLink', () => this.actionSelect());

            this.events['click a[data-action="clearLink"]'] = (e) => {
                let id = $(e.currentTarget).attr('data-id');

                this.deleteLink(id);

                // noinspection JSUnresolvedReference
                this.$element.get(0).focus({preventScroll: true});
            };
        }

        /** @type {Object.<string, *>} */
        this.panelDefs = this.getMetadata()
            .get(['clientDefs', this.entityType, 'relationshipPanels', this.name]) || {};
    }

    /**
     * Copy values from a model to view properties.
     */
    copyValuesFromModel() {
        this.ids = Espo.Utils.clone(this.model.get(this.idsName) || []);
        this.nameHash = Espo.Utils.clone(this.model.get(this.nameHashName) || {});
    }

    /**
     * Handle a search type.
     *
     * @protected
     * @param {string} type A type.
     */
    handleSearchType(type) {
        if (~['anyOf', 'noneOf', 'allOf'].indexOf(type)) {
            this.$el.find('div.link-group-container').removeClass('hidden');
        }
        else {
            this.$el.find('div.link-group-container').addClass('hidden');
        }
    }

    /** @inheritDoc */
    setupSearch() {
        this.events = _.extend({
            'change select.search-type': (e) => {
                let type = $(e.currentTarget).val();

                this.handleSearchType(type);
            },
        }, this.events || {});
    }

    /**
     * Get an autocomplete max record number. Can be extended.
     *
     * @protected
     * @return {number}
     */
    getAutocompleteMaxCount() {
        if (this.autocompleteMaxCount) {
            return this.autocompleteMaxCount;
        }

        return this.getConfig().get('recordsPerPage');
    }

    /**
     * Compose an autocomplete URL. Can be extended.
     *
     * @protected
     * @return {string|Promise<string>}
     */
    getAutocompleteUrl() {
        let url = this.foreignScope + '?&maxSize=' + this.getAutocompleteMaxCount();

        if (!this.forceSelectAllAttributes) {
            /** @var {Object.<string, *>} */
            const panelDefs = this.getMetadata()
                .get(['clientDefs', this.entityType, 'relationshipPanels', this.name]) || {};

            const mandatorySelectAttributeList = this.mandatorySelectAttributeList ||
                panelDefs.selectMandatoryAttributeList;

            let select = ['id', 'name'];

            if (mandatorySelectAttributeList) {
                select = select.concat(mandatorySelectAttributeList);
            }

            url += '&select=' + select.join(',')
        }

        if (this.panelDefs.selectHandler) {
            return new Promise(resolve => {
                this._getSelectFilters().then(filters => {
                    if (filters.bool) {
                        url += '&' + $.param({'boolFilterList': filters.bool});
                    }

                    if (filters.primary) {
                        url += '&' + $.param({'primaryFilter': filters.primary});
                    }

                    if (filters.advanced) {
                        url += '&' + $.param({'where': filters.advanced});
                    }

                    resolve(url);
                });
            });
        }

        const boolList = [
            ...(this.getSelectBoolFilterList() || []),
            ...(this.panelDefs.selectBoolFilterList || []),
        ];

        if (boolList.length) {
            url += '&' + $.param({'boolFilterList': boolList});
        }

        const primary = this.getSelectPrimaryFilterName() || this.panelDefs.selectPrimaryFilterName;

        if (primary) {
            url += '&' + $.param({'primaryFilter': primary});
        }

        return url;
    }

    /** @inheritDoc */
    afterRender() {
        if (this.isEditMode() || this.isSearchMode()) {
            this.$element = this.$el.find('input.main-element');

            let $element = this.$element;

            if (!this.autocompleteDisabled) {
                this.$element.on('blur', () => {
                    setTimeout(() => this.$element.autocomplete('clear'), 300);
                });

                const minChar = this.autocompleteOnEmpty ? 0 : 1;

                this.$element.autocomplete({
                    lookup: (q, callback) => {
                        Promise.resolve(this.getAutocompleteUrl(q))
                            .then(url => {
                                Espo.Ajax
                                    .getRequest(url, {q: q})
                                    .then(response => {
                                        callback(this._transformAutocompleteResult(response));
                                    });
                            });
                    },
                    minChars: minChar,
                    paramName: 'q',
                    noCache: true,
                    autoSelectFirst: true,
                    triggerSelectOnValidInput: false,
                    beforeRender: $c => {
                        if (this.$element.hasClass('input-sm')) {
                            $c.addClass('small');
                        }
                    },
                    formatResult: suggestion => {
                        // noinspection JSUnresolvedReference
                        return this.getHelper().escapeString(suggestion.name);
                    },
                    transformResult: response => {
                        response = JSON.parse(response);

                        let list = [];

                        response.list.forEach((item) => {
                            list.push({
                                id: item.id,
                                name: item.name || item.id,
                                data: item.id,
                                value: item.name || item.id,
                            });
                        });

                        return {
                            suggestions: list
                        };
                    },
                    onSelect: s => {
                        this.getModelFactory().create(this.foreignScope, model => {
                            model.set(s.attributes);

                            this.select([model])

                            this.$element.val('');
                            this.$element.focus();
                        });
                    },
                });

                this.$element.attr('autocomplete', 'espo-' + this.name);

                this.once('render', () => {
                    $element.autocomplete('dispose');
                });

                this.once('remove', () => {
                    $element.autocomplete('dispose');
                });
            }

            $element.on('change', () => {
                $element.val('');
            });

            this.renderLinks();

            if (this.isEditMode()) {
                if (this.sortable) {
                    // noinspection JSUnresolvedReference
                    this.$el.find('.link-container').sortable({
                        stop: () => {
                            this.fetchFromDom();
                            this.trigger('change');
                        },
                    });
                }
            }

            if (this.isSearchMode()) {
                let type = this.$el.find('select.search-type').val();

                this.handleSearchType(type);

                this.$el.find('select.search-type').on('change', () => {
                    this.trigger('change');
                });
            }
        }
    }

    /**
     * Render items.
     *
     * @protected
     */
    renderLinks() {
        this.ids.forEach(id => {
            this.addLinkHtml(id, this.nameHash[id]);
        });
    }

    /**
     * Delete an item.
     *
     * @protected
     * @param {string} id An ID.
     */
    deleteLink(id) {
        this.trigger('delete-link', id);
        this.trigger('delete-link:' + id);

        this.deleteLinkHtml(id);

        let index = this.ids.indexOf(id);

        if (index > -1) {
            this.ids.splice(index, 1);
        }

        delete this.nameHash[id];

        this.afterDeleteLink(id);
        this.trigger('change');
    }

    /**
     * Add an item.
     *
     * @protected
     * @param {string} id An ID.
     * @param {string} name A name.
     */
    addLink(id, name) {
        if (!~this.ids.indexOf(id)) {
            this.ids.push(id);

            this.nameHash[id] = name;

            this.addLinkHtml(id, name);
            this.afterAddLink(id);

            this.trigger('add-link', id);
            this.trigger('add-link:' + id);
        }

        this.trigger('change');
    }

    /**
     * @protected
     * @param {string} id An ID.
     */
    afterDeleteLink(id) {}

    /**
     * @protected
     * @param {string} id An ID.
     */
    afterAddLink(id) {}

    /**
     * @protected
     * @param {string} id An ID.
     */
    deleteLinkHtml(id) {
        this.$el.find('.link-' + id).remove();
    }

    /**
     * Add an item for edit mode.
     *
     * @protected
     * @param {string} id An ID.
     * @param {string} name A name.
     * @return {JQuery|null}
     */
    addLinkHtml(id, name) {
        // Do not use the `html` method to avoid XSS.

        name = name || id;

        let $container = this.$el.find('.link-container');

        let $el = $('<div>')
            .addClass('link-' + id)
            .addClass('list-group-item')
            .attr('data-id', id);

        $el.text(name).append('&nbsp;');

        $el.prepend(
            $('<a>')
                .addClass('pull-right')
                .attr('role', 'button')
                .attr('tabindex', '0')
                .attr('data-id', id)
                .attr('data-action', 'clearLink')
                .append(
                    $('<span>').addClass('fas fa-times')
                )
        );

        $container.append($el);

        return $el;
    }

    // noinspection JSUnusedLocalSymbols
    /**
     * @param {string} id An ID.
     * @return {string}
     */
    getIconHtml(id) {
        return this.iconHtml;
    }

    /**
     * Get an item HTML for detail mode.
     *
     * @param {string} id An ID.
     * @param {string} [name] A name.
     * @return {string}
     */
    getDetailLinkHtml(id, name) {
        // Do not use the `html` method to avoid XSS.

        name = name || this.nameHash[id] || id;

        if (!name && id) {
            name = this.translate(this.foreignScope, 'scopeNames');
        }

        let iconHtml = this.isDetailMode() ?
            this.getIconHtml(id) : '';

        let $a = $('<a>')
            .attr('href', this.getUrl(id))
            .attr('data-id', id)
            .text(name);

        if (iconHtml) {
            $a.prepend(iconHtml)
        }

        return $a.get(0).outerHTML;
    }

    /**
     * @protected
     * @param {string} id An ID.
     * @return {string}
     */
    getUrl(id) {
        return '#' + this.foreignScope + '/view/' + id;
    }

    /** @inheritDoc */
    getValueForDisplay() {
        if (!this.isDetailMode() && !this.isListMode()) {
            return null;
        }

        let itemList = [];

        this.ids.forEach(id => {
            itemList.push(this.getDetailLinkHtml(id));
        });

        if (!itemList.length) {
            return null;
        }

        return itemList
            .map(item => $('<div>')
                .addClass('link-multiple-item')
                .html(item)
                .wrap('<div />').parent().html()
            )
            .join('');
    }

    /** @inheritDoc */
    validateRequired() {
        if (!this.isRequired()) {
            return false;
        }

        let idList = this.model.get(this.idsName) || [];

        if (idList.length === 0) {
            let msg = this.translate('fieldIsRequired', 'messages')
                .replace('{field}', this.getLabelText());

            this.showValidationMessage(msg);

            return true;
        }

        return false;
    }

    /** @inheritDoc */
    fetch() {
        let data = {};

        data[this.idsName] = Espo.Utils.clone(this.ids);
        data[this.nameHashName] = Espo.Utils.clone(this.nameHash);

        return data;
    }

    /** @inheritDoc */
    fetchFromDom() {
        this.ids = [];

        this.$el.find('.link-container').children().each((i, li) => {
            let id = $(li).attr('data-id');

            if (!id) {
                return;
            }

            this.ids.push(id);
        });
    }

    /** @inheritDoc */
    fetchSearch() {
        let type = this.$el.find('select.search-type').val();
        let idList = this.ids || [];

        if (~['anyOf', 'allOf', 'noneOf'].indexOf(type) && !idList.length) {
            return {
                type: 'isNotNull',
                attribute: 'id',
                data: {
                    type: type,
                },
            };
        }

        let data;

        if (type === 'anyOf') {
            data = {
                type: 'linkedWith',
                value: idList,
                data: {
                    type: type,
                    nameHash: this.nameHash,
                },
            };

            return data;
        }

        if (type === 'allOf') {
            data = {
                type: 'linkedWithAll',
                value: idList,
                data: {
                    type: type,
                    nameHash: this.nameHash,
                },
            };

            if (!idList.length) {
                data.value = null;
            }

            return data;
        }

        if (type === 'noneOf') {
            data = {
                type: 'notLinkedWith',
                value: idList,
                data: {
                    type: type,
                    nameHash: this.nameHash,
                },
            };

            return data;
        }

        if (type === 'isEmpty') {
            data = {
                type: 'isNotLinked',
                data: {
                    type: type,
                },
            };

            return data;
        }

        if (type === 'isNotEmpty') {
            data = {
                type: 'isLinked',
                data: {
                    type: type,
                },
            };

            return data;
        }
    }

    /** @inheritDoc */
    getSearchType() {
        return this.getSearchParamsData().type ||
            this.searchParams.typeFront ||
            this.searchParams.type || 'anyOf';
    }

    /**
     * @protected
     * @param {string} id
     */
    quickView(id) {
        let entityType = this.foreignScope;

        let helper = new RecordModal(this.getMetadata(), this.getAcl());

        helper.showDetail(this, {
            id: id,
            scope: entityType,
        });
    }

    /**
     * @protected
     */
    actionSelect() {
        Espo.Ui.notify(' ... ');

        const panelDefs = this.panelDefs;

        const viewName = panelDefs.selectModalView ||
            this.getMetadata().get(`clientDefs.${this.foreignScope}.modalViews.select`) ||
            this.selectRecordsView;

        const mandatorySelectAttributeList = this.mandatorySelectAttributeList ||
            panelDefs.selectMandatoryAttributeList;

        const createButton = this.isEditMode() &&
            (!this.createDisabled && !panelDefs.createDisabled || this.forceCreateButton);

        let createAttributesProvider = null;

        if (createButton) {
            createAttributesProvider = () => {
                let attributes = this.getCreateAttributes() || {};

                if (!panelDefs.createHandler) {
                    return Promise.resolve(attributes);
                }

                return new Promise(resolve => {
                    Espo.loader.requirePromise(panelDefs.createHandler)
                        .then(Handler => new Handler(this.getHelper()))
                        .then(handler => {
                            handler.getAttributes(this.model)
                                .then(additionalAttributes => {
                                    resolve({
                                        ...attributes,
                                        ...additionalAttributes,
                                    });
                                });
                        });
                });
            };
        }

        this._getSelectFilters().then(filters => {
            this.createView('dialog', viewName, {
                scope: this.foreignScope,
                createButton: createButton,
                filters: filters.advanced,
                boolFilterList: filters.bool,
                primaryFilterName: filters.primary,
                filterList: this.getSelectFilterList(),
                multiple: true,
                mandatorySelectAttributeList: mandatorySelectAttributeList,
                forceSelectAllAttributes: this.forceSelectAllAttributes,
                createAttributesProvider: createAttributesProvider,
                layoutName: this.panelDefs.selectLayout,
            }, dialog => {
                dialog.render();

                Espo.Ui.notify(false);

                this.listenToOnce(dialog, 'select', models => {
                    this.clearView('dialog');

                    if (Object.prototype.toString.call(models) !== '[object Array]') {
                        models = [models];
                    }

                    this.select(models);
                });
            });
        });
    }

    /**
     * On records select.
     *
     * @protected
     * @param {module:model[]} models
     * @since 8.0.4
     */
    select(models) {
        models.forEach(model => {
            this.addLink(model.id, model.get('name'));
        });
    }

    /**
     * @private
     * @return {Promise<{bool?: string[], advanced?: Object, primary?: string}>}
     */
    _getSelectFilters() {
        const handler = this.panelDefs.selectHandler;

        const localBoolFilterList = this.getSelectBoolFilterList();

        if (!handler || this.isSearchMode()) {
            const boolFilterList = (localBoolFilterList || this.panelDefs.selectBoolFilterList) ?
                [
                    ...(localBoolFilterList || []),
                    ...(this.panelDefs.selectBoolFilterList || []),
                ] :
                undefined;

            return Promise.resolve({
                primary: this.getSelectPrimaryFilterName() || this.panelDefs.selectPrimaryFilterName,
                bool: boolFilterList,
                advanced: this.getSelectFilters() || undefined,
            });
        }

        return new Promise(resolve => {
            Espo.loader.requirePromise(handler)
                .then(Handler => new Handler(this.getHelper()))
                .then(/** module:handlers/select-related */handler => {
                    return handler.getFilters(this.model);
                })
                .then(filters => {
                    const advanced = {...(this.getSelectFilters() || {}), ...(filters.advanced || {})};
                    const primaryFilter = this.getSelectPrimaryFilterName() ||
                        filters.primary || this.panelDefs.selectPrimaryFilterName;

                    const boolFilterList = (localBoolFilterList || filters.bool || this.panelDefs.selectBoolFilterList) ?
                        [
                            ...(localBoolFilterList || []),
                            ...(filters.bool || []),
                            ...(this.panelDefs.selectBoolFilterList || []),
                        ] :
                        undefined;

                    resolve({
                        bool: boolFilterList,
                        primary: primaryFilter,
                        advanced: advanced,
                    });
                });
        });
    }

    /**
     * @private
     */
    _transformAutocompleteResult(response) {
        const list = [];

        response.list.forEach(item => {
            list.push({
                id: item.id,
                name: item.name || item.id,
                data: item.id,
                value: item.name || item.id,
                attributes: item,
            });
        });

        return {suggestions: list};
    }
}

export default LinkMultipleFieldView;
PK]]����views/fields/currency-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import EnumFieldView from 'views/fields/enum';

class CurrencyListFieldView extends EnumFieldView {

    setupOptions() {
        this.params.options = [];

        (this.getConfig().get('currencyList') || []).forEach(item => {
            this.params.options.push(item);
        });
    }
}

export default CurrencyListFieldView;
PK]��z���+views/fields/link-multiple-category-tree.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import LinkMultipleFieldView from 'views/fields/link-multiple';

class LinkMultipleCategoryTreeFieldView extends LinkMultipleFieldView {

    selectRecordsView = 'views/modals/select-category-tree-records'
    autocompleteDisabled = false

    getUrl(id) {
        return '#' + this.entityType + '/list/categoryId=' + id;
    }

    fetchSearch() {
        const data = super.fetchSearch();

        if (!data) {
            return data;
        }

        data.type = 'inCategory';

        return data;
    }
}

// noinspection JSUnusedGlobalSymbols
export default LinkMultipleCategoryTreeFieldView;

PK]4*��rrviews/fields/followers.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import LinkMultipleFieldView from 'views/fields/link-multiple';

class FollowersFieldView extends LinkMultipleFieldView {

    foreignScope = 'User'
    portionSize = 6

    setup() {
        super.setup();

        this.addActionHandler('showMoreFollowers', (e, target) => {
            this.showMoreFollowers();

            $(target).remove();
        });

        this.portionSize = this.getConfig().get('recordFollowersLoadLimit') || this.portionSize;

        this.limit = this.portionSize;

        this.listenTo(this.model, 'change:isFollowed', () => {
            let idList = this.model.get(this.idsName) || [];

            if (this.model.get('isFollowed')) {
                if (!~idList.indexOf(this.getUser().id)) {
                    idList.unshift(this.getUser().id);

                    let nameMap = this.model.get(this.nameHashName) || {};

                    nameMap[this.getUser().id] = this.getUser().get('name');

                    this.model.trigger('change:' + this.idsName);

                    this.reRender();
                }

                return;
            }

            let index = idList.indexOf(this.getUser().id);

            if (~index) {
                idList.splice(index, 1);

                this.model.trigger('change:' + this.idsName);

                this.reRender();
            }
        });
    }

    /*reloadFollowers() {
        this.getCollectionFactory().create('User', collection => {
            collection.url = this.model.entityType + '/' + this.model.id + '/followers';
            collection.offset = 0;
            collection.maxSize = this.limit;

            this.listenToOnce(collection, 'sync', () => {
                let idList = [];
                let nameMap = {};

                collection.forEach(user => {
                    idList.push(user.id);
                    nameMap[user.id] = user.get('name');
                });

                this.model.set(this.idsName, idList);
                this.model.set(this.nameHashName, nameMap);

                this.reRender();
            });

            collection.fetch();
        });
    }*/

    showMoreFollowers() {
        this.getCollectionFactory().create('User', collection => {
            collection.url = this.model.entityType + '/' + this.model.id + '/followers';
            collection.offset = this.ids.length || 0;
            collection.maxSize = this.portionSize;
            collection.data.select = ['id', 'name'].join(',');
            collection.orderBy = null;
            collection.order = null;

            this.listenToOnce(collection, 'sync', () => {
                let idList = this.model.get(this.idsName) || [];
                let nameMap = this.model.get(this.nameHashName) || {};

                collection.forEach(user => {
                    idList.push(user.id);
                    nameMap[user.id] = user.get('name');
                });

                this.limit += this.portionSize;

                this.model.trigger('change:' + this.idsName);

                this.reRender();
            });

            collection.fetch();
        });
    }

    getValueForDisplay() {
        if (this.mode === this.MODE_DETAIL || this.mode === this.MODE_LIST) {
            let list = [];

            this.ids.forEach(id => {
                list.push(this.getDetailLinkHtml(id));
            });

            let str = null;

            if (list.length) {
                str = '' + list.join(', ') + '';
            }

            if (list.length >= this.limit) {
                str += ', <a role="button" data-action="showMoreFollowers">...</a>';
            }

            return str;
        }
    }
}

export default FollowersFieldView;
PK]MA��<�<views/fields/date.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/fields/date */

import BaseFieldView from 'views/fields/base';
import moment from 'moment';

/**
 * A date field.
 */
class DateFieldView extends BaseFieldView {

    type = 'date'

    listTemplate = 'fields/date/list'
    listLinkTemplate = 'fields/date/list-link'
    detailTemplate = 'fields/date/detail'
    editTemplate = 'fields/date/edit'
    searchTemplate = 'fields/date/search'

    validations = ['required', 'date', 'after', 'before']

    searchTypeList = [
        'lastSevenDays', 'ever', 'isEmpty', 'currentMonth', 'lastMonth', 'nextMonth', 'currentQuarter',
        'lastQuarter', 'currentYear', 'lastYear', 'today', 'past', 'future', 'lastXDays', 'nextXDays',
        'olderThanXDays', 'afterXDays', 'on', 'after', 'before', 'between',
    ]

    initialSearchIsNotIdle = true

    setup() {
       super.setup();

        if (this.getConfig().get('fiscalYearShift')) {
            this.searchTypeList = Espo.Utils.clone(this.searchTypeList);

            if (this.getConfig().get('fiscalYearShift') % 3 !== 0) {
                this.searchTypeList.push('currentFiscalQuarter');
                this.searchTypeList.push('lastFiscalQuarter');
            }

            this.searchTypeList.push('currentFiscalYear');
            this.searchTypeList.push('lastFiscalYear');
        }
    }

    data() {
        let data = super.data();

        data.dateValue = this.getDateStringValue();

        data.isNone = data.dateValue === null;

        if (data.dateValue === -1) {
            data.dateValue = null;
            data.isLoading = true;
        }

        if (this.isSearchMode()) {
            let value = this.getSearchParamsData().value || this.searchParams.dateValue;
            let valueTo = this.getSearchParamsData().valueTo || this.searchParams.dateValueTo;

            data.dateValue = this.getDateTime().toDisplayDate(value);
            data.dateValueTo = this.getDateTime().toDisplayDate(valueTo);

            if (~['lastXDays', 'nextXDays', 'olderThanXDays', 'afterXDays']
                    .indexOf(this.getSearchType())
            ) {
                data.number = this.searchParams.value;
            }
        }

        return data;
    }

    setupSearch() {
        this.events = _.extend({
            'change select.search-type': (e) => {
                let type = $(e.currentTarget).val();

                this.handleSearchType(type);
            },
        }, this.events || {});
    }

    stringifyDateValue(value) {
        if (!value) {
            if (
                this.mode === this.MODE_EDIT ||
                this.mode === this.MODE_SEARCH ||
                this.mode === this.MODE_LIST ||
                this.mode === this.MODE_LIST_LINK
            ) {
                return '';
            }

            return null;
        }

        if (
            this.mode === this.MODE_LIST ||
            this.mode === this.MODE_DETAIL ||
            this.mode === this.MODE_LIST_LINK
        ) {
            return this.convertDateValueForDetail(value);
        }

        return this.getDateTime().toDisplayDate(value);
    }

    convertDateValueForDetail(value) {
        if (this.getConfig().get('readableDateFormatDisabled') || this.params.useNumericFormat) {
            return this.getDateTime().toDisplayDate(value);
        }

        let timezone = this.getDateTime().getTimeZone();
        let internalDateTimeFormat = this.getDateTime().internalDateTimeFormat;
        let readableFormat = this.getDateTime().getReadableDateFormat();
        let valueWithTime = value + ' 00:00:00';

        let today = moment().tz(timezone).startOf('day');
        let dateTime = moment.tz(valueWithTime, internalDateTimeFormat, timezone);

        var temp = today.clone();

        var ranges = {
            'today': [temp.unix(), temp.add(1, 'days').unix()],
            'tomorrow': [temp.unix(), temp.add(1, 'days').unix()],
            'yesterday': [temp.add(-3, 'days').unix(), temp.add(1, 'days').unix()],
        };

        if (dateTime.unix() >= ranges['today'][0] && dateTime.unix() < ranges['today'][1]) {
            return this.translate('Today');
        }

        if (dateTime.unix() >= ranges['tomorrow'][0] && dateTime.unix() < ranges['tomorrow'][1]) {
            return this.translate('Tomorrow');
        }

        if (dateTime.unix() >= ranges['yesterday'][0] && dateTime.unix() < ranges['yesterday'][1]) {
            return this.translate('Yesterday');
        }

        // Need to use UTC, otherwise there's a DST issue with old dates.
        dateTime = moment.utc(valueWithTime, internalDateTimeFormat);

        if (dateTime.format('YYYY') === today.format('YYYY')) {
            return dateTime.format(readableFormat);
        }

        return dateTime.format(readableFormat + ', YYYY');
    }

    getDateStringValue() {
        if (this.mode === this.MODE_DETAIL && !this.model.has(this.name)) {
            return -1;
        }

        var value = this.model.get(this.name);

        return this.stringifyDateValue(value);
    }

    afterRender() {
        if (this.mode === this.MODE_EDIT || this.mode === this.MODE_SEARCH) {
            this.$element = this.$el.find('[data-name="' + this.name + '"]');

            let wait = false;

            // @todo Introduce ui/date-picker.

            this.$element.on('change', (e) => {
                if (!wait) {
                    this.trigger('change');
                    wait = true;
                    setTimeout(() => wait = false, 100);
                }

                if (e.isTrigger) {
                    if (document.activeElement !== this.$element.get(0)) {
                        this.$element.focus();
                    }
                }
            });

            this.$element.on('click', () => {
                this.$element.datepicker('show');
            });

            let options = {
                format: this.getDateTime().dateFormat.toLowerCase(),
                weekStart: this.getDateTime().weekStart,
                autoclose: true,
                todayHighlight: true,
                keyboardNavigation: true,
                todayBtn: this.getConfig().get('datepickerTodayButton') || false,
                orientation: 'bottom auto',
                templates: {
                    leftArrow: '<span class="fas fa-chevron-left fa-sm"></span>',
                    rightArrow: '<span class="fas fa-chevron-right fa-sm"></span>',
                },
                container: this.$el.closest('.modal-body').length ?
                    this.$el.closest('.modal-body') :
                    'body',
            };

            let language = this.getConfig().get('language');

            if (!(language in $.fn.datepicker.dates)) {
                $.fn.datepicker.dates[language] = {
                    days: this.translate('dayNames', 'lists'),
                    daysShort: this.translate('dayNamesShort', 'lists'),
                    daysMin: this.translate('dayNamesMin', 'lists'),
                    months: this.translate('monthNames', 'lists'),
                    monthsShort: this.translate('monthNamesShort', 'lists'),
                    today: this.translate('Today'),
                    clear: this.translate('Clear'),
                };
            }

            options.language = language;

            this.$element.datepicker(options);

            if (this.mode === this.MODE_SEARCH) {
                let $elAdd = this.$el.find('input.additional');

                $elAdd.datepicker(options);

                $elAdd.parent().find('button.date-picker-btn').on('click', () => {
                    $elAdd.datepicker('show');
                });

                this.$el.find('select.search-type').on('change', () => {
                    this.trigger('change');
                });

                this.$el.find('input.number').on('change', () => {
                    this.trigger('change');
                });

                $elAdd.on('change', e => {
                    this.trigger('change');

                    if (e.isTrigger) {
                        if (document.activeElement !== $elAdd.get(0)) {
                            $elAdd.focus();
                        }
                    }
                });

                $elAdd.on('click', () => {
                    $elAdd.datepicker('show');
                });
            }

            this.$element.parent().find('button.date-picker-btn').on('click', () => {
                this.$element.datepicker('show');
            });

            if (this.mode === this.MODE_SEARCH) {
                let $searchType = this.$el.find('select.search-type');

                this.handleSearchType($searchType.val());
            }
        }
    }

    handleSearchType(type) {
        this.$el.find('div.primary').addClass('hidden');
        this.$el.find('div.additional').addClass('hidden');
        this.$el.find('div.additional-number').addClass('hidden');

        if (~['on', 'notOn', 'after', 'before'].indexOf(type)) {
            this.$el.find('div.primary').removeClass('hidden');
        }
        else if (~['lastXDays', 'nextXDays', 'olderThanXDays', 'afterXDays'].indexOf(type)) {
            this.$el.find('div.additional-number').removeClass('hidden');
        }
        else if (type === 'between') {
            this.$el.find('div.primary').removeClass('hidden');
            this.$el.find('div.additional').removeClass('hidden');
        }
    }

    parseDate(string) {
        return this.getDateTime().fromDisplayDate(string);
    }

    /**
     * @param {string} string
     * @return {string|-1|null}
     */
    parse(string) {
        if (!string) {
            return null;
        }

        return this.parseDate(string);
    }

    /** @inheritDoc */
    fetch() {
        let data = {};

        data[this.name] = this.parse(this.$element.val());

        return data;
    }

    /** @inheritDoc */
    fetchSearch() {
        let value = this.parseDate(this.$element.val());

        let type = this.fetchSearchType();
        let data;

        if (type === 'between') {
            if (!value) {
                return null;
            }

            let valueTo = this.parseDate(this.$el.find('input.additional').val());

            if (!valueTo) {
                return null;
            }

            data = {
                type: type,
                value: [value, valueTo],
                data: {
                    value: value,
                    valueTo: valueTo
                },
            };
        } else if (~['lastXDays', 'nextXDays', 'olderThanXDays', 'afterXDays'].indexOf(type)) {
            let number = this.$el.find('input.number').val();

            data = {
                type: type,
                value: number,
            };
        }
        else if (~['on', 'notOn', 'after', 'before'].indexOf(type)) {
            if (!value) {
                return null;
            }

            data = {
                type: type,
                value: value,
                data: {
                    value: value,
                },
            };
        }
        else if (type === 'isEmpty') {
            data = {
                type: 'isNull',
                data: {
                    type: type,
                },
            };
        }
        else {
            data = {
                type: type,
            };
        }

        return data;
    }

    getSearchType() {
        return this.getSearchParamsData().type || this.searchParams.typeFront || this.searchParams.type;
    }

    validateRequired() {
        if (!this.isRequired()) {
            return;
        }

        if (this.model.get(this.name) === null) {
            let msg = this.translate('fieldIsRequired', 'messages')
                .replace('{field}', this.getLabelText());

            this.showValidationMessage(msg);

            return true;
        }
    }

    // noinspection JSUnusedGlobalSymbols
    validateDate() {
        if (this.model.get(this.name) === -1) {
            let msg = this.translate('fieldShouldBeDate', 'messages')
                .replace('{field}', this.getLabelText());

            this.showValidationMessage(msg);

            return true;
        }
    }

    // noinspection JSUnusedGlobalSymbols
    validateAfter() {
        let field = this.model.getFieldParam(this.name, 'after');

        if (!field) {
            return false;
        }

        let value = this.model.get(this.name);
        let otherValue = this.model.get(field);

        if (!(value && otherValue)) {
            return;
        }

        if (moment(value).unix() <= moment(otherValue).unix()) {
            let msg = this.translate('fieldShouldAfter', 'messages')
                .replace('{field}', this.getLabelText())
                .replace('{otherField}', this.translate(field, 'fields', this.entityType));

            this.showValidationMessage(msg);

            return true;
        }
    }

    // noinspection JSUnusedGlobalSymbols
    validateBefore() {
        let field = this.model.getFieldParam(this.name, 'before');

        if (!field) {
            return false;
        }

        let value = this.model.get(this.name);
        let otherValue = this.model.get(field);

        if (!(value && otherValue)) {
            return;
        }

        if (moment(value).unix() >= moment(otherValue).unix()) {
            let msg = this.translate('fieldShouldBefore', 'messages')
                .replace('{field}', this.getLabelText())
                .replace('{otherField}', this.translate(field, 'fields', this.entityType));

            this.showValidationMessage(msg);

            return true;
        }
    }
}

export default DateFieldView;
PK]��Jڊ	�	 views/fields/entity-type-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import MultiEnumFieldView from 'views/fields/multi-enum';

class EntityTypeListFieldView extends MultiEnumFieldView {

    checkAvailability(entityType) {
        const defs = this.scopesMetadataDefs[entityType] || {};

        if (defs.entity && defs.object) {
            return true;
        }
    }

    setupOptions() {
        const scopes = this.scopesMetadataDefs = this.getMetadata().get('scopes');

        this.params.options = Object.keys(scopes)
            .filter(scope => {
                if (this.checkAvailability(scope)) {
                    return true;
                }
            })
            .sort((v1, v2) => {
                 return this.translate(v1, 'scopeNames')
                     .localeCompare(this.translate(v2, 'scopeNames'));
            });
    }

    setup() {
        if (!this.params.translation) {
            this.params.translation = 'Global.scopeNames';
        }

        this.setupOptions();

        super.setup();
    }
}

export default EntityTypeListFieldView;
PK]U0�E�	�	!views/fields/foreign-checklist.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ChecklistFieldView from 'views/fields/checklist';

class ForeignChecklistFieldView extends ChecklistFieldView {

    type = 'foreign'

    setupOptions() {
        this.params.options = [];

        if (!this.params.field || !this.params.link) {
            return;
        }

        const scope = this.getMetadata()
            .get(['entityDefs', this.model.entityType, 'links', this.params.link, 'entity']);

        if (!scope) {
            return;
        }

        this.params.isSorted = this.getMetadata()
            .get(['entityDefs', scope, 'fields', this.params.field, 'isSorted']) || false;

        this.params.options = this.getMetadata()
            .get(['entityDefs', scope, 'fields', this.params.field, 'options']) || [];

        this.translatedOptions = {};

        this.params.options.forEach(item => {
            this.translatedOptions[item] = this.getLanguage()
                .translateOption(item, this.params.field, scope);
        });
    }
}

export default ForeignChecklistFieldView;
PK]Y0���views/fields/range-float.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import RangeIntFieldView from 'views/fields/range-int';
import FloatFieldView from 'views/fields/float';

class RangeFloatFieldView extends RangeIntFieldView {

    type = 'rangeFloat'

    validations = ['required', 'float', 'range', 'order']
    decimalPlacesRawValue = 10

    setupAutoNumericOptions() {
        this.autoNumericOptions = {
            digitGroupSeparator: this.thousandSeparator || '',
            decimalCharacter: this.decimalMark,
            modifyValueOnWheel: false,
            selectOnFocus: false,
            decimalPlaces: this.decimalPlacesRawValue,
            decimalPlacesRawValue: this.decimalPlacesRawValue,
            allowDecimalPadding: false,
            showWarnings: false,
            formulaMode: true,
        };
    }

    // noinspection JSUnusedGlobalSymbols
    validateFloat() {
        const validate = (name) => {
            if (isNaN(this.model.get(name))) {
                let msg = this.translate('fieldShouldBeFloat', 'messages')
                    .replace('{field}', this.getLabelText());

                this.showValidationMessage(msg, '[data-name="' + name + '"]');

                return true;
            }
        };

        let result = false;

        result = validate(this.fromField) || result;
        result = validate(this.toField) || result;

        return result;
    }

    parse(value) {
        return FloatFieldView.prototype.parse.call(this, value);
    }

    formatNumber(value) {
        return FloatFieldView.prototype.formatNumberDetail.call(this, value);
    }
}

export default RangeFloatFieldView;

PK]�Xc*.*.views/fields/int.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/fields/int */

import BaseFieldView from 'views/fields/base';
import AutoNumeric from 'autonumeric';

/**
 * An integer field.
 */
class IntFieldView extends BaseFieldView {

    type = 'int'

    listTemplate = 'fields/int/list'
    detailTemplate = 'fields/int/detail'
    editTemplate = 'fields/int/edit'
    searchTemplate = 'fields/int/search'
    validations = ['required', 'int', 'range']

    thousandSeparator = ','

    searchTypeList = [
        'isNotEmpty',
        'isEmpty',
        'equals',
        'notEquals',
        'greaterThan',
        'lessThan',
        'greaterThanOrEquals',
        'lessThanOrEquals',
        'between',
    ]

    /**
     * @type {Object.<string, *>|null}
     * @protected
     */
    autoNumericOptions = null

    /**
     * @type {?AutoNumeric}
     * @protected
     */
    autoNumericInstance = null

    setup() {
        super.setup();

        this.setupMaxLength();

        if (this.getPreferences().has('thousandSeparator')) {
            this.thousandSeparator = this.getPreferences().get('thousandSeparator');
        }
        else if (this.getConfig().has('thousandSeparator')) {
            this.thousandSeparator = this.getConfig().get('thousandSeparator');
        }

        if (this.params.disableFormatting) {
            this.disableFormatting = true;
        }
    }

    setupFinal() {
        super.setupFinal();

        this.setupAutoNumericOptions();
    }

    /**
     * @protected
     */
    setupAutoNumericOptions() {
        let separator = (!this.disableFormatting ? this.thousandSeparator : null) || '';
        let decimalCharacter = '.';

        if (separator === '.') {
            decimalCharacter = ',';
        }

        this.autoNumericOptions = {
            digitGroupSeparator: separator,
            decimalCharacter: decimalCharacter,
            modifyValueOnWheel: false,
            decimalPlaces: 0,
            selectOnFocus: false,
            formulaMode: true,
        };
    }

    afterRender() {
        super.afterRender();

        if (this.mode === this.MODE_EDIT) {
            if (this.autoNumericOptions) {
                this.autoNumericInstance = new AutoNumeric(this.$element.get(0), this.autoNumericOptions);
            }
        }

        if (this.mode === this.MODE_SEARCH) {
            let $searchType = this.$el.find('select.search-type');

            this.handleSearchType($searchType.val());

            this.$el.find('select.search-type').on('change', () => {
                this.trigger('change');
            });

            this.$element.on('input', () => {
                this.trigger('change');
            });

            let $inputAdditional = this.$el.find('input.additional');

            $inputAdditional.on('input', () => {
                this.trigger('change');
            });

            if (this.autoNumericOptions) {
                new AutoNumeric(this.$element.get(0), this.autoNumericOptions);
                new AutoNumeric($inputAdditional.get(0), this.autoNumericOptions);
            }
        }
    }

    data() {
        let data = super.data();

        if (this.model.get(this.name) !== null && typeof this.model.get(this.name) !== 'undefined') {
            data.isNotEmpty = true;
        }

        data.valueIsSet = this.model.has(this.name);

        if (this.isSearchMode()) {
            data.value = this.searchParams.value;

            if (this.getSearchType() === 'between') {
                data.value = this.getSearchParamsData().value1 || this.searchParams.value1;
                data.value2 = this.getSearchParamsData().value2 || this.searchParams.value2;
            }
        }

        if (this.isEditMode()) {
            data.value = this.model.get(this.name);
        }

        return data;
    }

    getValueForDisplay() {
        let value = isNaN(this.model.get(this.name)) ? null : this.model.get(this.name);

        return this.formatNumber(value);
    }

    formatNumber(value) {
        if (this.disableFormatting) {
            return value;
        }

        return this.formatNumberDetail(value);
    }

    formatNumberDetail(value) {
        if (value === null) {
            return '';
        }

        let stringValue = value.toString();

        stringValue = stringValue.replace(/\B(?=(\d{3})+(?!\d))/g, this.thousandSeparator);

        return stringValue;
    }

    setupSearch() {
        this.events['change select.search-type'] = e => {
            this.handleSearchType($(e.currentTarget).val());
        };
    }

    handleSearchType(type) {
        var $additionalInput = this.$el.find('input.additional');

        var $input = this.$el.find('input[data-name="'+this.name+'"]');

        if (type === 'between') {
            $additionalInput.removeClass('hidden');
            $input.removeClass('hidden');
        }
        else if (~['isEmpty', 'isNotEmpty'].indexOf(type)) {
            $additionalInput.addClass('hidden');
            $input.addClass('hidden');
        }
        else {
            $additionalInput.addClass('hidden');
            $input.removeClass('hidden');
        }
    }

    getMaxValue() {
        var maxValue = this.model.getFieldParam(this.name, 'max') || null;

        if (!maxValue && maxValue !== 0) {
            maxValue = null;
        }

        if ('max' in this.params) {
            maxValue = this.params.max;
        }

        return maxValue;
    }

    getMinValue() {
        var minValue = this.model.getFieldParam(this.name, 'min');

        if (!minValue && minValue !== 0) {
            minValue = null;
        }

        if ('min' in this.params) {
            minValue = this.params.min;
        }

        return minValue;
    }

    setupMaxLength() {
        var maxValue = this.getMaxValue();

        if (typeof max !== 'undefined' && max !== null) {
            maxValue = this.formatNumber(maxValue);

            this.params.maxLength = maxValue.toString().length;
        }
    }

    validateInt() {
        let value = this.model.get(this.name);

        if (isNaN(value)) {
            let msg = this.translate('fieldShouldBeInt', 'messages').replace('{field}', this.getLabelText());

            this.showValidationMessage(msg);

            return true;
        }
    }

    validateRange() {
        let value = this.model.get(this.name);

        if (value === null) {
            return false;
        }

        let minValue = this.getMinValue();
        let maxValue = this.getMaxValue();

        if (minValue !== null && maxValue !== null) {
            if (value < minValue || value > maxValue ) {
                let msg = this.translate('fieldShouldBeBetween', 'messages')
                    .replace('{field}', this.getLabelText())
                    .replace('{min}', minValue)
                    .replace('{max}', maxValue);

                this.showValidationMessage(msg);

                return true;
            }
        }
        else {
            if (minValue !== null) {
                if (value < minValue) {
                    let msg = this.translate('fieldShouldBeGreater', 'messages')
                        .replace('{field}', this.getLabelText())
                        .replace('{value}', minValue);

                    this.showValidationMessage(msg);

                    return true;
                }
            }
            else if (maxValue !== null) {
                if (value > maxValue) {
                    let msg = this.translate('fieldShouldBeLess', 'messages')
                        .replace('{field}', this.getLabelText())
                        .replace('{value}', maxValue);
                    this.showValidationMessage(msg);

                    return true;
                }
            }
        }
    }

    validateRequired() {
        if (this.isRequired()) {
            let value = this.model.get(this.name);

            if (value === null || value === false) {
                let msg = this.translate('fieldIsRequired', 'messages')
                    .replace('{field}', this.getLabelText());

                this.showValidationMessage(msg);

                return true;
            }
        }
    }

    parse(value) {
        value = (value !== '') ? value : null;

        if (value === null) {
            return null;
        }

        value = value
            .split(this.thousandSeparator)
            .join('');

        if (value.indexOf('.') !== -1 || value.indexOf(',') !== -1) {
            return NaN;
        }

        return parseInt(value);
    }

    fetch() {
        let value = this.$element.val();
        value = this.parse(value);

        let data = {};

        data[this.name] = value;

        return data;
    }

    fetchSearch() {
        let value = this.parse(this.$element.val());

        let type = this.fetchSearchType();

        let data;

        if (isNaN(value)) {
            return false;
        }

        if (type === 'between') {
            let valueTo = this.parse(this.$el.find('input.additional').val());

            if (isNaN(valueTo)) {
                return false;
            }

            data = {
                type: type,
                value: [value, valueTo],
                data: {
                    value1: value,
                    value2: valueTo
                }
            };
        }
        else if (type === 'isEmpty') {
            data = {
                type: 'isNull',
                typeFront: 'isEmpty'
            };
        }
        else if (type === 'isNotEmpty') {
            data = {
                type: 'isNotNull',
                typeFront: 'isNotEmpty'
            };
        }
        else {
            data = {
                type: type,
                value: value,
                data: {
                    value1: value
                }
            };
        }

        return data;
    }

    getSearchType() {
        return this.searchParams.typeFront || this.searchParams.type;
    }
}

export default IntFieldView;
PK]"��Ed	d	views/fields/entity-type.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import EnumFieldView from 'views/fields/enum';

class EntityTypeFieldView extends EnumFieldView {

    checkAvailability(entityType) {
        const defs = this.scopesMetadataDefs[entityType] || {};

        if (defs.entity && defs.object) {
            return true;
        }
    }

    setupOptions() {
        const scopes = this.scopesMetadataDefs = this.getMetadata().get('scopes');

        this.params.options = Object.keys(scopes)
            .filter(scope => {
                if (this.checkAvailability(scope)) {
                    return true;
                }
            })
            .sort((v1, v2) => {
                 return this.translate(v1, 'scopeNames')
                     .localeCompare(this.translate(v2, 'scopeNames'));
            });

        this.params.options.unshift('');
    }

    setup() {
        this.params.translation = 'Global.scopeNames';
        this.setupOptions();

        super.setup();
    }
}

export default EntityTypeFieldView;
PK]�n��U0U0views/fields/duration.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import EnumFieldView from 'views/fields/enum';
import Select from 'ui/select';
import moment from 'moment';

class DurationFieldView extends EnumFieldView {

    type = 'duration'

    listTemplate = 'fields/base/detail'
    detailTemplate = 'fields/varchar/detail'
    editTemplate = 'fields/duration/edit'

    data() {
        let valueIsSet = this.model.has(this.startField) && this.model.has(this.endField);

        return {
            valueIsSet: valueIsSet,
            durationOptions: this.durationOptions,
            ...super.data(),
        };
    }

    calculateSeconds() {
        this.seconds = 0;

        let start = this.model.get(this.startField);
        let end = this.model.get(this.endField);

        if (this.isEditMode() || this.isDetailMode()) {
            if (this.model.isNew()) {
                this.seconds = this.model.getFieldParam(this.name, 'default') || 0;
            }
        }

        if (this.model.get('isAllDay')) {
            let startDate = this.model.get(this.startField + 'Date');
            let endDate = this.model.get(this.endField + 'Date');

            if (startDate && endDate) {
                this.seconds = moment(endDate).add(1,'days').unix() - moment(startDate).unix();

                return;
            }
        }

        if (start && end) {
            this.seconds = moment(this.model.get(this.endField)).unix() -
                moment(this.model.get(this.startField)).unix();

            return;
        }

        if (start) {
            end = this._getDateEnd();

            this.model.set(this.endField, end, {silent: true});
        }
    }

    init() {
        super.init();

        this.listenTo(this, 'render', () => {
            this.calculateSeconds();

            this.durationOptions = '';

            this.getOptions().forEach(d => {
                let $o = $('<option>')
                    .val(d)
                    .text(this.stringifyDuration(d));

                if (d === this.seconds) {
                    $o.attr('selected', 'selected')
                }

                this.durationOptions += $o.get(0).outerHTML;
            });

            this.stringValue = this.stringifyDuration(this.seconds);
        });
    }

    /**
     * @return {Number[]}
     */
    getOptions() {
        let options = Espo.Utils.clone(this.model.getFieldParam(this.name, 'options') ?? []);

        if (!this.model.get('isAllDay') && options.indexOf(this.seconds) === -1) {
            options.push(this.seconds);
        }

        options.sort((a, b) => a - b);

        return options;
    }

    setup() {
        this.startField = this.model.getFieldParam(this.name, 'start');
        this.endField = this.model.getFieldParam(this.name, 'end');

        if (!this.startField || !this.endField) {
            throw new Error('Bad definition for field \'' + this.name + '\'.');
        }

        this.calculateSeconds();

        this.blockDateEndChangeListener = false;

        this.listenTo(this.model, 'change:' + this.endField, (m, v, o) => {
            if (this.blockDateEndChangeListener) {
                return;
            }

            let start = this.model.get(this.startField);
            let end = this.model.get(this.endField);

            if (!end || !start) {
                return;
            }

            this.seconds = moment(end).unix() - moment(start).unix();

            if (o.updatedByDuration) {
                return;
            }

            this.updateDuration();
        });

        this.listenTo(this.model, 'change:' + this.startField, (m, v, o) => {
            if (o.ui) {
                let isAllDay = this.model.get(this.startField + 'Date');

                if (isAllDay) {
                    let remainder = this.seconds % (3600 * 24);

                    if (remainder !== 0) {
                        this.seconds = this.seconds - remainder + 3600 * 24;
                    }
                }

                this.blockDateEndChangeListener = true;
                setTimeout(() => this.blockDateEndChangeListener = false, 100);

                this.updateDateEnd();

                setTimeout(() => this.updateDuration(), 50);

                return;
            }

            if (!this.isEditMode() && o.xhr) {
                return;
            }

            this.updateDateEnd();
        });
    }

    getValueForDisplay() {
        return this.stringValue;
    }

    stringifyDuration(secondsTotal) {
        if (!secondsTotal) {
            return '0';
        }

        if (secondsTotal < 60) {
            return '0';
        }

        let d = secondsTotal;
        let days = Math.floor(d / (86400));
        d = d % (86400);

        let hours = Math.floor(d / (3600));
        d = d % (3600);
        let minutes = Math.floor(d / (60));

        let parts = [];

        if (days) {
            parts.push(days + '' + this.getLanguage().translate('d', 'durationUnits'));
        }

        if (hours) {
            parts.push(hours + '' + this.getLanguage().translate('h', 'durationUnits'));
        }

        if (minutes) {
            parts.push(minutes + '' + this.getLanguage().translate('m', 'durationUnits'));
        }

        return parts.join(' ');
    }

    focusOnInlineEdit() {
        Select.focus(this.$duration);
    }

    afterRender() {
        let parentView = this.getParentView();

        if (parentView && 'getView' in parentView) {
            this.endFieldView = parentView.getView(this.endField);
        }

        if (this.isEditMode()) {
            this.$duration = this.$el.find('.main-element');

            this.$duration.on('change', () => {
                this.seconds = parseInt(this.$duration.val());

                this.updateDateEnd();
            });

            let start = this.model.get(this.startField);
            let end = this.model.get(this.endField);

            let seconds = this.$duration.val();

            if (!end && start && seconds) {
                if (this.endFieldView) {
                    if (this.endFieldView.isRendered()) {
                        this.updateDateEnd();
                    }
                    else {
                        this.endFieldView.once('after:render', () => {
                            this.updateDateEnd();
                        });
                    }
                }
            }

            Select.init(this.$duration, {
                sortBy: '$score',
                sortDirection: 'desc',
                /**
                 * @param {string} search
                 * @param {{value: string}} item
                 * @return {number}
                 */
                score: (search, item) => {
                    let num = parseInt(item.value);
                    let searchNum = parseInt(search);

                    if (isNaN(searchNum)) {
                        return 0;
                    }

                    let numOpposite = Number.MAX_SAFE_INTEGER - num;

                    if (searchNum === 0 && num === 0) {
                        return numOpposite;
                    }

                    if (searchNum * 60 === num) {
                        return numOpposite;
                    }

                    if (searchNum * 60 * 60 === num) {
                        return numOpposite;
                    }

                    if (searchNum * 60 * 60 * 24 === num) {
                        return numOpposite;
                    }

                    return 0;
                },
                load: (item, callback) => {
                    let num = parseInt(item);

                    if (isNaN(num) || num <= 0) {
                        return;
                    }

                    if (num > 59) {
                        return;
                    }

                    let list = [];

                    let mSeconds = num * 60;

                    list.push({
                        value: mSeconds.toString(),
                        text: this.stringifyDuration(mSeconds),
                    });

                    if (num <= 9) {
                        let hSeconds = num * 3600;

                        list.push({
                            value: hSeconds.toString(),
                            text: this.stringifyDuration(hSeconds),
                        });
                    }

                    callback(list);
                },
            });
        }
    }

    _getDateEndDate() {
        let seconds = this.seconds;
        let start = this.model.get(this.startField + 'Date');

        if (!start) {
            return;
        }

        if (!seconds) {
            return start;
        }

        let endUnix = moment.utc(start).unix() + seconds;

        return moment.unix(endUnix)
            .utc()
            .add(-1, 'day')
            .format(this.getDateTime().internalDateFormat);
    }

    _getDateEnd() {
        let seconds = this.seconds;
        let start = this.model.get(this.startField);

        if (!start) {
            return;
        }

        let endUnix;
        let end;

        if (seconds) {
            endUnix = moment.utc(start).unix() + seconds;

            end = moment.unix(endUnix).utc().format(this.getDateTime().internalDateTimeFormat);
        }
        else {
            end = start;
        }

        return end;
    }

    updateDateEnd() {
        let end;

        if (this.model.get('isAllDay')) {
            end = this._getDateEndDate();

            setTimeout(() => {
                this.model.set(this.endField + 'Date', end, {updatedByDuration: true});
            }, 1);

            return;
        }

        end = this._getDateEnd();

        setTimeout(() => {
            this.model.set(this.endField, end, {updatedByDuration: true});
            this.model.set(this.endField + 'Date', null);
        }, 1);
    }

    updateDuration() {
        let seconds = this.seconds;

        if (this.isEditMode() && this.$duration && this.$duration.length) {
            let options = this.getOptions().map(value => {
                return {
                    value: value.toString(),
                    text: this.stringifyDuration(value),
                };
            });

            Select.setValue(this.$duration, '');
            Select.setOptions(this.$duration, options);
            Select.setValue(this.$duration, seconds.toString());

            return;
        }

        this.reRender();
    }

    fetch() {
        // noinspection JSValidateTypes
        return {};
    }
}

export default DurationFieldView;
PK]�Ix�̉̉views/fields/wysiwyg.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/fields/wysiwyg */

import TextFieldView from 'views/fields/text';

/**
 * A wysiwyg field.
 */
class WysiwygFieldView extends TextFieldView {

    type = 'wysiwyg'

    listTemplate = 'fields/wysiwyg/detail'
    detailTemplate = 'fields/wysiwyg/detail'
    editTemplate = 'fields/wysiwyg/edit'

    height = 250
    rowsDefault = 10000
    fallbackBodySideMargin = 5
    fallbackBodyTopMargin = 4
    seeMoreDisabled = true
    fetchEmptyValueAsNull = false
    validationElementSelector = '.note-editor'
    htmlPurificationDisabled = false
    tableClassName = 'table table-bordered'

    events = {
        /** @this WysiwygFieldView */
        'click .note-editable': function () {
            this.fixPopovers();
        },
        /** @this WysiwygFieldView */
        'focus .note-editable': function () {
            this.$noteEditor.addClass('in-focus');
        },
        /** @this WysiwygFieldView */
        'blur .note-editable': function () {
            this.$noteEditor.removeClass('in-focus');
        },
    }

    setup() {
        super.setup();

        this.wait(
            Espo.loader.requirePromise('lib!summernote')
                .then(() => {
                    if (!$.summernote.options || 'espoImage' in $.summernote.options) {
                        return;
                    }

                    this.initEspoPlugin();
                })
        );

        this.hasBodyPlainField = !!~this.getFieldManager()
            .getEntityTypeFieldList(this.model.entityType)
            .indexOf(this.name + 'Plain');

        if ('height' in this.params) {
            this.height = this.params.height;
        }

        if ('minHeight' in this.params) {
            this.minHeight = this.params.minHeight;
        }

        this.useIframe = this.params.useIframe || this.useIframe;

        this.setupToolbar();

        this.listenTo(this.model, 'change:isHtml', (model, value, o) => {
            if (o.ui && this.isEditMode()) {
                if (!this.isRendered()) {
                    return;
                }

                if (!model.has('isHtml') || model.get('isHtml')) {
                    let value = this.plainToHtml(this.model.get(this.name));

                    if (
                        this.lastHtmlValue &&
                        this.model.get(this.name) === this.htmlToPlain(this.lastHtmlValue)
                    ) {
                        value = this.lastHtmlValue;
                    }

                    this.model.set(this.name, value, {skipReRender: true});
                    this.enableWysiwygMode();

                    return;
                }

                this.lastHtmlValue = this.model.get(this.name);

                let value = this.htmlToPlain(this.model.get(this.name));

                this.disableWysiwygMode();

                this.model.set(this.name, value);

                return;
            }

            if (this.isDetailMode()) {
                if (this.isRendered()) {
                    this.reRender();
                }
            }
        });

        this.once('remove', () => {
            this.destroySummernote();
        });

        this.on('inline-edit-off', () => {
            this.destroySummernote();
        });

        this.on('render', () => {
            this.destroySummernote();
        });

        this.once('remove', () => {
            $(window).off('resize.' + this.cid);

            if (this.$scrollable) {
                this.$scrollable.off('scroll.' + this.cid + '-edit');
            }
        });
    }

    data() {
        const data = super.data();

        data.useIframe = this.useIframe;
        data.isPlain = this.isPlain();

        // noinspection JSValidateTypes
        return data;
    }

    setupToolbar() {
        this.buttons = {};

        this.toolbar = this.params.toolbar || this.toolbar || [
            ['style', ['style']],
            ['style', ['bold', 'italic', 'underline', 'clear']],
            ['fontsize', ['fontsize']],
            ['color', ['color']],
            ['para', ['ul', 'ol', 'paragraph']],
            ['height', ['height']],
            ['table', ['table', 'espoLink', 'espoImage', 'hr']],
            ['misc', ['codeview', 'fullscreen']],
        ];

        if (this.params.toolbar) {
            return;
        }

        if (!this.params.attachmentField) {
            return;
        }

        this.toolbar.push(['attachment', ['attachment']]);

        this.buttons['attachment'] = () => {
            let ui = $.summernote.ui;

            let button = ui.button({
                contents: '<i class="fas fa-paperclip"></i>',
                tooltip: this.translate('Attach File'),
                click: () => {
                    this.attachFile();

                    this.listenToOnce(this.model, 'attachment-uploaded:attachments', () => {
                        if (this.isEditMode()) {
                            Espo.Ui.success(this.translate('Attached'));
                        }
                    });
                }
            });

            return button.render();
        };
    }

    isPlain() {
        return this.model.has('isHtml') && !this.model.get('isHtml');
    }

    fixPopovers() {
        $('body > .note-popover').removeClass('hidden');
    }

    getValueForDisplay() {
        let value = super.getValueForDisplay();

        if (this.isPlain()) {
            return value;
        }

        return this.sanitizeHtml(value);
    }

    sanitizeHtml(value) {
        if (value) {
            if (!this.htmlPurificationDisabled) {
                value = this.getHelper().sanitizeHtml(value);
            } else {
                value = this.sanitizeHtmlLight(value);
            }
        }

        return value || '';
    }

    sanitizeHtmlLight(value) {
       return this.getHelper().moderateSanitizeHtml(value);
    }

    getValueForEdit() {
        let value = this.model.get(this.name) || '';

        if (this.htmlPurificationForEditDisabled) {
            return this.sanitizeHtmlLight(value);
        }

        return this.sanitizeHtml(value);
    }

    afterRender() {
        super.afterRender();

        if (this.isEditMode()) {
            this.$summernote = this.$el.find('.summernote');
        }

        let language = this.getConfig().get('language');

        if (!(language in $.summernote.lang)) {
            $.summernote.lang[language] = this.getLanguage().translate('summernote', 'sets');
        }

        if (this.isEditMode()) {
            if (!this.model.has('isHtml') || this.model.get('isHtml')) {
                this.enableWysiwygMode();
            }
            else {
                this.$element.removeClass('hidden');
            }
        }

        if (this.isReadMode()) {
            this.renderDetail();
        }
    }

    renderDetail() {
        if (this.model.has('isHtml') && !this.model.get('isHtml')) {
            this.$el.find('.plain').removeClass('hidden');

            return;

        }

        if (!this.useIframe) {
            this.$element = this.$el.find('.html-container');

            return;
        }

        this.$el.find('iframe').removeClass('hidden');

        let $iframe = this.$el.find('iframe');

        /** @type {HTMLIFrameElement} */
        const iframeElement = this.iframe = $iframe.get(0);

        if (!iframeElement) {
            return;
        }

        $iframe.on('load', () => {
            $iframe.contents().find('a').attr('target', '_blank');
        });

        let documentElement = iframeElement.contentWindow.document;

        let body = this.sanitizeHtml(this.model.get(this.name) || '');

        let useFallbackStylesheet = this.getThemeManager().getParam('isDark') && this.htmlHasColors(body);

        let $iframeContainer = $iframe.parent();

        useFallbackStylesheet ?
            $iframeContainer.addClass('fallback') :
            $iframeContainer.removeClass('fallback');

        let linkElement = iframeElement.contentWindow.document.createElement('link');

        linkElement.type = 'text/css';
        linkElement.rel = 'stylesheet';
        linkElement.href = this.getBasePath() + (
            useFallbackStylesheet ?
            this.getThemeManager().getIframeFallbackStylesheet() :
            this.getThemeManager().getIframeStylesheet()
        );

        body = linkElement.outerHTML + body;

        documentElement.write(body);
        documentElement.close();

        let $body = $iframe.contents().find('html body');

        $body.find('img').each((i, img) => {
            let $img = $(img);

            if ($img.css('max-width') !== 'none') {
                return;
            }

            $img.css('max-width', '100%');
        });

        let $document = $(documentElement);

        // Make dropdowns closed.
        $document.on('click', () => {
            let event = new MouseEvent('click', {
                bubbles: true,
            });

            $iframe[0].dispatchEvent(event);
        });

        // Make notifications & global-search popup closed.
        $document.on('mouseup', () => {
            let event = new MouseEvent('mouseup', {
                bubbles: true,
            });

            $iframe[0].dispatchEvent(event);
        });

        // Make shortcuts working.
        $document.on('keydown', e => {
            const originalEvent = /** @type {KeyboardEvent} */ e.originalEvent;

            const event = new KeyboardEvent('keydown', {
                bubbles: true,
                code: originalEvent.code,
                ctrlKey: originalEvent.ctrlKey,
                metaKey: originalEvent.metaKey,
                altKey: originalEvent.altKey,
            });

            $iframe[0].dispatchEvent(event);
        });

        let processWidth = function () {
            let bodyElement = $body.get(0);

            if (bodyElement) {
                if (bodyElement.clientWidth !== iframeElement.scrollWidth) {
                    iframeElement.style.height = (iframeElement.scrollHeight + 20) + 'px';
                }
            }
        };

        if (useFallbackStylesheet) {
            $iframeContainer.css({
                paddingLeft: this.fallbackBodySideMargin + 'px',
                paddingRight: this.fallbackBodySideMargin + 'px',
                paddingTop: this.fallbackBodyTopMargin + 'px',
            });
        }

        let increaseHeightStep = 10;

        let processIncreaseHeight = function (iteration, previousDiff) {
            $body.css('height', '');

            iteration = iteration || 0;

            if (iteration > 200) {
                return;
            }

            iteration ++;

            let diff = $document.height() - iframeElement.scrollHeight;

            if (typeof previousDiff !== 'undefined') {
                if (diff === previousDiff) {
                    $body.css('height', (iframeElement.clientHeight - increaseHeightStep) + 'px');
                    processWidth();

                    return;
                }
            }

            if (diff) {
                let height = iframeElement.scrollHeight + increaseHeightStep;

                iframeElement.style.height = height + 'px';
                processIncreaseHeight(iteration, diff);
            }
            else {
                processWidth();
            }
        };

        let processBg = () => {
            let color = iframeElement.contentWindow.getComputedStyle($body.get(0)).backgroundColor;

            $iframeContainer.css({
                backgroundColor: color,
            });
        };

        let processHeight = function (isOnLoad) {
            if (!isOnLoad) {
                $iframe.css({
                    overflowY: 'hidden',
                    overflowX: 'hidden'
                });

                iframeElement.style.height = '0px';
            }
            else {
                if (iframeElement.scrollHeight >= $document.height()) {
                    return;
                }
            }

            let $body = $iframe.contents().find('html body');
            let height = $body.height();

            if (height === 0) {
                height = $body.children().height() + 100;
            }

            iframeElement.style.height = height + 'px';

            processIncreaseHeight();

            if (!isOnLoad) {
                $iframe.css({
                    overflowY: 'hidden',
                    overflowX: 'scroll'
                });
            }
        };

        $iframe.css({
            visibility: 'hidden'
        });

        setTimeout(() => {
            processHeight();

            $iframe.css({
                visibility: 'visible'
            });

            $iframe.on('load', () => {
                processHeight(true);

                if (useFallbackStylesheet) {
                    processBg();
                }
            });
        }, 40);

        if (!this.model.get(this.name)) {
            $iframe.addClass('hidden');
        }

        let windowWidth = $(window).width();

        $(window).off('resize.' + this.cid);
        $(window).on('resize.' + this.cid, () => {
            if ($(window).width() !== windowWidth) {
                processHeight();
                windowWidth = $(window).width();
            }
        });
    }

    enableWysiwygMode() {
        if (!this.$element) {
            return;
        }

        this.$element.addClass('hidden');
        this.$summernote.removeClass('hidden');

        let contents = this.getValueForEdit();

        this.$summernote.html(contents);

        this.$summernote.find('style').remove();
        this.$summernote.find('link[ref="stylesheet"]').remove();

        let keyMap = Espo.Utils.cloneDeep($.summernote.options.keyMap);

        keyMap.pc['CTRL+K'] = 'espoLink.show';
        keyMap.mac['CMD+K'] = 'espoLink.show';
        keyMap.pc['CTRL+DELETE'] = 'removeFormat';
        keyMap.mac['CMD+DELETE']  = 'removeFormat';

        delete keyMap.pc['CTRL+ENTER'];
        delete keyMap.mac['CMD+ENTER'];
        delete keyMap.pc['CTRL+BACKSLASH'];
        delete keyMap.mac['CMD+BACKSLASH'];

        const toolbar = this.toolbar;

        let lastChangeKeydown = new Date();
        const changeKeydownInterval = this.changeInterval * 1000;

        // noinspection JSUnusedGlobalSymbols
        const options = {
            espoView: this,
            lang: this.getConfig().get('language'),
            keyMap: keyMap,
            callbacks: {
                onImageUpload: (files) => {
                    let file = files[0];

                    Espo.Ui.notify(this.translate('Uploading...'));

                    this.uploadInlineAttachment(file)
                        .then(attachment => {
                            let url = '?entryPoint=attachment&id=' + attachment.id;
                            this.$summernote.summernote('insertImage', url);

                            Espo.Ui.notify(false);
                        });
                },
                onBlur: () => {
                    this.trigger('change');
                },
                onKeydown: () => {
                    if (Date.now() - lastChangeKeydown > changeKeydownInterval) {
                        this.trigger('change');
                        lastChangeKeydown = Date.now();
                    }
                },
            },
            onCreateLink(link) {
                return link;
            },
            toolbar: toolbar,
            buttons: this.buttons,
            dialogsInBody: this.$el,
            codeviewFilter: true,
            tableClassName: this.tableClassName,
        };

        if (this.height) {
            options.height = this.height;
        }
        else {
            let $scrollable = this.$el.closest('.modal-body');

            if (!$scrollable.length) {
                $scrollable = $(window);
            }

            this.$scrollable = $scrollable;

            $scrollable.off('scroll.' + this.cid + '-edit');
            $scrollable.on('scroll.' + this.cid + '-edit', (e) => {
                this.onScrollEdit(e);
            });
        }

        if (this.minHeight) {
            options.minHeight = this.minHeight;
        }

        this.destroySummernote();

        this.$summernote.summernote(options);
        this.summernoteIsInitialized = true;

        this.$toolbar = this.$el.find('.note-toolbar');
        this.$area = this.$el.find('.note-editing-area');

        this.$noteEditor = this.$el.find('> .note-editor');
    }

    focusOnInlineEdit() {
        if (this.$noteEditor)  {
            this.$summernote.summernote('focus');

            return;
        }

        super.focusOnInlineEdit();
    }

    uploadInlineAttachment(file) {
        return new Promise((resolve, reject) => {
            this.getModelFactory().create('Attachment', attachment => {
                let fileReader = new FileReader();

                fileReader.onload = (e) => {
                    attachment.set('name', file.name);
                    attachment.set('type', file.type);
                    attachment.set('role', 'Inline Attachment');
                    attachment.set('global', true);
                    attachment.set('size', file.size);

                    if (this.model.id) {
                        attachment.set('relatedId', this.model.id);
                    }

                    attachment.set('relatedType', this.model.entityType);
                    attachment.set('file', e.target.result);
                    attachment.set('field', this.name);

                    attachment
                        .save()
                        .then(() => resolve(attachment))
                        .catch(() => reject());
                };

                fileReader.readAsDataURL(file);
            });
        });
    }

    destroySummernote() {
        if (this.summernoteIsInitialized && this.$summernote) {
            this.$summernote.summernote('destroy');
            this.summernoteIsInitialized = false;
        }
    }

    plainToHtml(html) {
        html = html || '';

        return html.replace(/\n/g, '<br>');
    }

    htmlToPlain(text) {
        text = text || '';

        let value = text
            .replace(/<br\s*\/?>/mg, '\n')
            .replace(/<\/p\s*\/?>/mg, '\n\n');

        let $div = $('<div>').html(value);

        $div.find('style').remove();
        $div.find('link[ref="stylesheet"]').remove();

        value =  $div.text();

        return value;
    }

    disableWysiwygMode() {
        this.destroySummernote();

        this.$noteEditor = null;

        if (this.$summernote) {
            this.$summernote.addClass('hidden');
        }

        this.$element.removeClass('hidden');

        if (this.$scrollable) {
            this.$scrollable.off('scroll.' + this.cid + '-edit');
        }
    }

    fetch() {
        let data = {};

        if (!this.model.has('isHtml') || this.model.get('isHtml')) {
            let code = this.$summernote.summernote('code');

            if (code === '<p><br></p>') {
                code = '';
            }

            let imageTagString = '<img src="' + window.location.origin + window.location.pathname +
                '?entryPoint=attachment';

            code = code.replace(
                new RegExp(imageTagString.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"), 'g'),
                '<img src="?entryPoint=attachment'
            );
            data[this.name] = code;
        }
        else {
            data[this.name] = this.$element.val();

            if (this.fetchEmptyValueAsNull) {
                if (!data[this.name]) {
                    data[this.name] = null;
                }
            }
        }

        if (this.model.has('isHtml') && this.hasBodyPlainField) {
            if (this.model.get('isHtml')) {
                data[this.name + 'Plain'] = this.htmlToPlain(data[this.name]);
            }
            else {
                data[this.name + 'Plain'] = data[this.name];
            }
        }

        return data;
    }

    onScrollEdit(e) {
        const $target = $(e.target);
        const toolbarHeight = this.$toolbar.height();
        const toolbarWidth = this.$toolbar.parent().width();
        let edgeTop, edgeTopAbsolute;

        // noinspection JSIncompatibleTypesComparison
        if ($target.get(0) === window.document) {
            const $buttonContainer = $target.find('.detail-button-container:not(.hidden)');
            const offset = $buttonContainer.offset();

            if (offset) {
                edgeTop = offset.top + $buttonContainer.height();
                edgeTopAbsolute = edgeTop - $(window).scrollTop();
            }
        }
        else {
            let offset = $target.offset();

            if (offset) {
                edgeTop = offset.top;
                edgeTopAbsolute = edgeTop - $(window).scrollTop();
            }
        }

        let top = this.$el.offset().top;
        let bottom = top + this.$el.height() - toolbarHeight;

        let toStick = false;

        if (edgeTop > top && bottom > edgeTop) {
            toStick = true;
        }

        if (toStick) {
            this.$toolbar.css({
                top: edgeTopAbsolute + 'px',
                width: toolbarWidth + 'px',
            });

            this.$toolbar.addClass('sticked');

            this.$area.css({
                marginTop: toolbarHeight + 'px',
                backgroundColor: ''
            });

            return;
        }

        this.$toolbar.css({
            top: '',
            width: '',
        });

        this.$toolbar.removeClass('sticked');

        this.$area.css({
            marginTop: '',
        });
    }

    attachFile() {
        let $form = this.$el.closest('.record');

        $form.find('.field[data-name="' + this.params.attachmentField + '"] input.file').click();
    }

    initEspoPlugin() {
        let langSets = this.getLanguage().get('Global', 'sets', 'summernote') || {
            image: {},
            link: {},
            video: {},
        };

        $.extend($.summernote.options, {
            espoImage: {
                icon: '<i class="note-icon-picture"/>',
                tooltip: langSets.image.image,
            },
            espoLink: {
                icon: '<i class="note-icon-link"/>',
                tooltip: langSets.link.link,
            },
        });

        $.extend($.summernote.plugins, {
            'espoImage': function (context) {
                let ui = $.summernote.ui;
                let options = context.options;
                let self = options.espoView;
                let lang = options.langInfo;

                if (!self) {
                    return;
                }

                context.memo('button.espoImage', () => {
                    let button = ui.button({
                        contents: options.espoImage.icon,
                        tooltip: options.espoImage.tooltip,
                        click() {
                            context.invoke('espoImage.show');
                        },
                    });

                    return button.render();
                });

                this.initialize = function () {};

                this.destroy = function () {
                    if (!self) {
                        return;
                    }

                    self.clearView('insertImageDialog');
                };

                this.show = function () {
                    self.createView('insertImageDialog', 'views/wysiwyg/modals/insert-image', {
                        labels: {
                            insert: lang.image.insert,
                            url: lang.image.url,
                            selectFromFiles: lang.image.selectFromFiles,
                        },
                    }, view => {
                        view.render();

                        self.listenToOnce(view, 'upload', (target) => {
                            self.$summernote.summernote('insertImagesOrCallback', target);
                        });

                        self.listenToOnce(view, 'insert', (target) => {
                            self.$summernote.summernote('insertImage', target);
                        });

                        self.listenToOnce(view, 'close', () => {
                            self.clearView('insertImageDialog');
                            self.fixPopovers();
                        });
                    });
                };
            },

            'linkDialog': function (context) {
                let options = context.options;
                let self = options.espoView;
                let lang = options.langInfo;

                if (!self) {
                    return;
                }

                this.show = function () {
                    let linkInfo = context.invoke('editor.getLinkInfo');

                    self.createView('dialogInsertLink', 'views/wysiwyg/modals/insert-link', {
                        labels: {
                            insert: lang.link.insert,
                            openInNewWindow: lang.link.openInNewWindow,
                            url: lang.link.url,
                            textToDisplay: lang.link.textToDisplay,
                        },
                        linkInfo: linkInfo,
                    }, view => {
                        view.render();

                        self.listenToOnce(view, 'insert', (data) => {
                            self.$summernote.summernote('createLink', data);
                        });

                        self.listenToOnce(view, 'close', () => {
                            self.clearView('dialogInsertLink');
                            self.fixPopovers();
                        });
                    });
                };
            },

            'espoLink': function (context) {
                let ui = $.summernote.ui;
                let options = context.options;
                let self = options.espoView;
                let lang = options.langInfo;

                if (!self) {
                    return;
                }

                let isMacLike = /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);

                context.memo('button.espoLink', function () {
                    let button = ui.button({
                        contents: options.espoLink.icon,
                        tooltip: options.espoLink.tooltip + ' (' + (isMacLike ? 'CMD+K': 'CTRL+K') +')',
                        click() {
                            context.invoke('espoLink.show');
                        },
                    });

                    return button.render();
                });

                this.initialize = function () {
                    this.$modalBody = self.$el.closest('.modal-body');

                    this.isInModal = this.$modalBody.length > 0;
                };

                this.destroy = function () {
                    if (!self) {
                        return;
                    }

                    self.clearView('dialogInsertLink');
                };

                this.show = function () {
                    let linkInfo = context.invoke('editor.getLinkInfo');

                    let container = this.isInModal ? this.$modalBody.get(0) : window;

                    self.createView('dialogInsertLink', 'views/wysiwyg/modals/insert-link', {
                        labels: {
                            insert: lang.link.insert,
                            openInNewWindow: lang.link.openInNewWindow,
                            url: lang.link.url,
                            textToDisplay: lang.link.textToDisplay,
                        },
                        linkInfo: linkInfo,
                    }, (view) => {
                        view.render();

                        self.listenToOnce(view, 'insert', (data) => {
                            let scrollY = ('scrollY' in container) ?
                                container.scrollY :
                                container.scrollTop;

                            self.$summernote.summernote('createLink', data);

                            setTimeout(() => container.scroll(0, scrollY), 20);
                        });

                        self.listenToOnce(view, 'close', () => {
                            self.clearView('dialogInsertLink');
                            self.fixPopovers();
                        });
                    });
                };
            },

            'fullscreen': function (context) {
                const options = context.options;
                const self = options.espoView;
                //let lang = options.langInfo;
                //let ui = $.summernote.ui;

                if (!self) {
                    return;
                }

                this.$window = $(window);
                this.$scrollbar = $('html, body');

                this.initialize = function () {
                    this.$editor = context.layoutInfo.editor;
                    this.$toolbar = context.layoutInfo.toolbar;
                    this.$editable = context.layoutInfo.editable;
                    this.$codable = context.layoutInfo.codable;

                    this.$modal = self.$el.closest('.modal');
                    this.isInModal = this.$modal.length > 0;
                };


                this.resizeTo = function (size) {
                    this.$editable.css('height', size.h);
                    this.$codable.css('height', size.h);

                    // noinspection SpellCheckingInspection
                    if (this.$codable.data('cmeditor')) {
                        // noinspection SpellCheckingInspection,JSUnresolvedReference
                        this.$codable.data('cmeditor').setsize(null, size.h);
                    }
                };

                this.onResize = function () {
                    this.resizeTo({
                        h: this.$window.height() - this.$toolbar.outerHeight(),
                    });
                };

                this.isFullscreen = function () {
                    return this.$editor.hasClass('fullscreen');
                };

                this.destroy = function () {
                    this.$window.off('resize.summernote' + self.cid);

                    if (this.isInModal) {
                        this.$modal.css('overflow-y', '');
                    }
                    else {
                        this.$scrollbar.css('overflow', '');
                    }
                };

                this.toggle = function () {
                    this.$editor.toggleClass('fullscreen');

                    if (this.isFullscreen()) {
                        this.$editable.data('orgHeight', this.$editable.css('height'));
                        this.$editable.data('orgMaxHeight', this.$editable.css('maxHeight'));
                        this.$editable.css('maxHeight', '');

                        this.$window
                            .on('resize.summernote' + self.cid, this.onResize.bind(this))
                            .trigger('resize');

                        if (this.isInModal) {
                            this.$modal.css('overflow-y', 'hidden');
                        }
                        else {
                            this.$scrollbar.css('overflow', 'hidden');
                        }

                        // noinspection JSUnusedGlobalSymbols
                        this._isFullscreen = true;
                    }
                    else {
                        this.$window.off('resize.summernote'  + self.cid);
                        this.resizeTo({ h: this.$editable.data('orgHeight') });
                        this.$editable.css('maxHeight', this.$editable.css('orgMaxHeight'));

                        if (this.isInModal) {
                            this.$modal.css('overflow-y', '');
                        } else {
                            this.$scrollbar.css('overflow', '');
                        }

                        // noinspection JSUnusedGlobalSymbols
                        this._isFullscreen = false;
                    }

                    context.invoke('toolbar.updateFullscreen', this.isFullscreen());
                };
            },
        });
    }

    htmlHasColors(string) {
        if (~string.indexOf('background-color:')) {
            return true;
        }

        if (~string.indexOf('color:')) {
            return true;
        }

        if (~string.indexOf('<font color="')) {
            return true;
        }

        return false;
    }
}

export default WysiwygFieldView;
PK]�qS77views/fields/formula.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import TextFieldView from 'views/fields/text';

/**
 * @type {{
 *     edit: import('ace-builds').edit,
 *     require: import('ace-builds').require,
 * }}
 */
let ace;

class FormulaFieldView extends TextFieldView {

    detailTemplate ='fields/formula/detail'
    editTemplate = 'fields/formula/edit'

    height = 300
    maxLineDetailCount = 80
    maxLineEditCount = 200
    insertDisabled = false
    checkSyntaxDisabled = false

    events = {
        /** @this FormulaFieldView */
        'click [data-action="addAttribute"]': function () {
            this.addAttribute();
        },
        /** @this FormulaFieldView */
        'click [data-action="addFunction"]': function () {
            this.addFunction();
        },
        /** @this FormulaFieldView */
        'click [data-action="checkSyntax"]': function () {
            this.checkSyntax();
        },
    }

    setup() {
        super.setup();

        this.height = this.options.height || this.params.height || this.height;

        this.maxLineDetailCount =
            this.options.maxLineDetailCount ||
            this.params.maxLineDetailCount ||
            this.maxLineDetailCount;

        this.maxLineEditCount =
            this.options.maxLineEditCount ||
            this.params.maxLineEditCount ||
            this.maxLineEditCount;

        this.targetEntityType =
            this.options.targetEntityType ||
            this.params.targetEntityType ||
            this.targetEntityType;

        this.insertDisabled = this.insertDisabled || this.options.insertDisabled;
        this.checkSyntaxDisabled = this.checkSyntaxDisabled || this.options.checkSyntaxDisabled;

        this.containerId = 'editor-' + Math.floor((Math.random() * 10000) + 1).toString();

        if (this.mode === this.MODE_EDIT || this.mode === this.MODE_DETAIL) {
            this.wait(
                this.requireAce()
            );
        }

        this.on('remove', () => {
            if (this.editor) {
                this.editor.destroy();
            }
        });
    }

    requireAce() {
        return Espo.loader.requirePromise('lib!ace')
            .then(lib => {
                ace = /** window.ace */lib;

                let list = [
                    Espo.loader.requirePromise('lib!ace-mode-javascript'),
                    Espo.loader.requirePromise('lib!ace-ext-language_tools'),
                ];

                if (this.getThemeManager().getParam('isDark')) {
                    list.push(
                        Espo.loader.requirePromise('lib!ace-theme-tomorrow_night')
                    );
                }

                return Promise.all(list);
            });
    }

    data() {
        let data = super.data();

        data.containerId = this.containerId;
        data.targetEntityType = this.targetEntityType;
        data.hasSide = !this.insertDisabled || !this.checkSyntaxDisabled;
        data.hasInsert = !this.insertDisabled;
        data.hasCheckSyntax = !this.checkSyntaxDisabled;

        return data;
    }

    afterRender() {
        super.afterRender();

        this.$editor = this.$el.find('#' + this.containerId);

        if (
            this.$editor.length &&
            (
                this.mode === this.MODE_EDIT ||
                this.mode === this.MODE_DETAIL ||
                this.mode === this.MODE_LIST
            )
        ) {
            this.$editor.css('fontSize', '14px');

            if (this.mode === this.MODE_EDIT) {
                this.$editor.css('minHeight', this.height + 'px');
            }

            let editor = this.editor = ace.edit(this.containerId);

            editor.setOptions({
                maxLines: this.mode === this.MODE_EDIT ?
                    this.maxLineEditCount :
                    this.maxLineDetailCount,
            });

            if (this.getThemeManager().getParam('isDark')) {
                editor.setOptions({
                    theme: 'ace/theme/tomorrow_night',
                });
            }

            if (this.isEditMode()) {
                editor.getSession().on('change', () => {
                    this.trigger('change', {ui: true});
                });

                editor.getSession().setUseWrapMode(true);
            }

            if (this.isReadMode()) {
                editor.setReadOnly(true);
                editor.renderer.$cursorLayer.element.style.display = "none";
                editor.renderer.setShowGutter(false);
            }

            editor.setShowPrintMargin(false);
            editor.getSession().setUseWorker(false);
            editor.commands.removeCommand('find');
            editor.setHighlightActiveLine(false);

            let JavaScriptMode = ace.require('ace/mode/javascript').Mode;

            editor.session.setMode(new JavaScriptMode());

            if (!this.insertDisabled && !this.isReadMode()) {
                this.initAutocomplete();
            }
        }
    }

    fetch() {
        let data = {};

        let value = this.editor.getValue();

        if (value === '') {
            value = null;
        }

        data[this.name] = value;

        return data;
    }

    addAttribute() {
        this.createView('dialog', 'views/admin/formula/modals/add-attribute', {
            scope: this.targetEntityType,
        }, view => {
            view.render();

            this.listenToOnce(view, 'add', (attribute) => {
                this.editor.insert(attribute);

                this.clearView('dialog');
            });
        });
    }

    addFunction() {
        this.createView('dialog', 'views/admin/formula/modals/add-function', {
            scope: this.targetEntityType,
            functionDataList: this.getFunctionDataList(),
        }, view => {
            view.render();

            this.listenToOnce(view, 'add', (string) => {
                this.editor.insert(string);

                this.clearView('dialog');
            });
        });
    }

    getFunctionDataList() {
        let list = Espo.Utils.clone(
            this.getMetadata().get(['app', 'formula', 'functionList']) || []
        );

        if (this.options.additionalFunctionDataList) {
            list = list.concat(this.options.additionalFunctionDataList);
        }

        let allowedFunctionList = /** @type string[] */this.options.allowedFunctionList;

        if (allowedFunctionList) {
            list = list.filter(/** {name: string} */item => {
                for (let func of allowedFunctionList) {
                    if (func.endsWith('\\') && item.name.startsWith(func)) {
                        return true;
                    }

                    if (item.name === func) {
                        return true;
                    }
                }

                return false;
            });
        }

        if (!this.targetEntityType) {
            list = list.filter(item => {
                if (item.name.indexOf('entity\\') === 0) {
                    return false;
                }

                return true;
            });
        }

        return list;
    }

    initAutocomplete() {
        let functionItemList = this.getFunctionDataList().filter(item => item.insertText);

        let attributeList = this.getFormulaAttributeList();

        ace.require('ace/ext/language_tools');

        this.editor.setOptions({
            enableBasicAutocompletion: true,
            enableLiveAutocompletion: true,
        });

        // noinspection JSUnusedGlobalSymbols
        let completer = {
            identifierRegexps: [/[\\a-zA-Z0-9{}\[\].$'"]/],

            getCompletions: function (editor, session, pos, prefix, callback) {
                let matchedFunctionItemList = functionItemList
                    .filter((originalItem) => {
                        let text = originalItem.name;

                        if (text.indexOf(prefix) === 0) {
                            return true;
                        }

                        let parts = text.split('\\');

                        if (parts[parts.length - 1].indexOf(prefix) === 0) {
                            return true;
                        }

                        return false;
                    });

                let itemList = matchedFunctionItemList.map((item) => {
                    return {
                        caption: item.name + '()',
                        value: item.insertText,
                        meta: item.returnType || null,
                        completer: {
                            insertMatch: (editor, data) => {
                                editor.completer.insertMatch({value: data.value});

                                let index = data.value.indexOf('(');

                                if (!~index) {
                                    return;
                                }

                                if (~data.value.indexOf('()')) {
                                    return;
                                }

                                let pos = editor.selection.getCursor();

                                editor.gotoLine(
                                    pos.row + 1,
                                    pos.column - data.value.length + index + 1
                                );
                            },
                        },
                    };
                });

                let matchedAttributeList = attributeList
                    .filter((item) => {
                        if (item.indexOf(prefix) === 0) {
                            return true;
                        }

                        return false;
                    });

                let itemAttributeList = matchedAttributeList.map((item) => {
                    return {
                        name: item,
                        value: item,
                        meta: 'attribute',
                    };
                });

                itemList = itemList.concat(itemAttributeList);

                callback(null, itemList);
            }
        };

        this.editor.completers = [completer];
    }

    getFormulaAttributeList() {
        if (!this.targetEntityType) {
            return [];
        }

        let attributeList = this.getFieldManager()
            .getEntityTypeAttributeList(this.targetEntityType)
            .concat(['id'])
            .sort();

        let links = this.getMetadata().get(['entityDefs', this.targetEntityType, 'links']) || {};

        let linkList = [];

        Object.keys(links).forEach((link) => {
            let type = links[link].type;

            if (!type) {
                return;
            }

            if (~['belongsToParent', 'hasOne', 'belongsTo'].indexOf(type)) {
                linkList.push(link);
            }
        });

        linkList.sort();

        linkList.forEach((link) => {
            let scope = links[link].entity;

            if (!scope) {
                return;
            }

            if (links[link].disabled) {
                return;
            }

            let linkAttributeList = this.getFieldManager()
                .getEntityTypeAttributeList(scope)
                .sort();

            linkAttributeList.forEach((item) => {
                attributeList.push(link + '.' + item);
            });
        });

        return attributeList;
    }

    checkSyntax() {
        let expression = this.editor.getValue();

        if (!expression) {
            Espo.Ui.success(
                this.translate('checkSyntaxSuccess', 'messages', 'Formula')
            );

            return;
        }

        Espo.Ajax
            .postRequest('Formula/action/checkSyntax', {expression: expression})
            .then(response => {
                if (response.isSuccess) {
                    Espo.Ui.success(
                        this.translate('checkSyntaxSuccess', 'messages', 'Formula')
                    );

                    return;
                }

                let message = this.translate('checkSyntaxError', 'messages', 'Formula');

                if (response.message) {
                    message += ' ' + response.message;
                }

                Espo.Ui.error(message);
            });
    }
}

export default FormulaFieldView;
PK]J���JJ"views/fields/currency-converted.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import CurrencyFieldView from 'views/fields/currency';

class CurrencyConvertedFieldView extends CurrencyFieldView {

    data() {
        let data = super.data();

        const currencyValue = this.getConfig().get('defaultCurrency');

        data.currencyValue = currencyValue;
        data.currencySymbol = this.getMetadata().get(['app', 'currency', 'symbolMap', currencyValue]) || '';

        return data;
    }
}

export default CurrencyConvertedFieldView;
PK]'��%�%views/fields/person-name.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/fields/person-name */

import VarcharFieldView from 'views/fields/varchar';
import Select from 'ui/select';

class PersonNameFieldView extends VarcharFieldView {

    type = 'personName'

    detailTemplate = 'fields/person-name/detail'
    editTemplate = 'fields/person-name/edit'
    // noinspection JSUnusedGlobalSymbols
    editTemplateLastFirst = 'fields/person-name/edit-last-first'
    // noinspection JSUnusedGlobalSymbols
    editTemplateLastFirstMiddle = 'fields/person-name/edit-last-first-middle'
    // noinspection JSUnusedGlobalSymbols
    editTemplateFirstMiddleLast = 'fields/person-name/edit-first-middle-last'

    /** @inheritDoc */
    validations = [
        'required',
        'pattern',
    ]

    data() {
        let data = super.data();

        data.ucName = Espo.Utils.upperCaseFirst(this.name);
        data.salutationValue = this.model.get(this.salutationField);
        data.firstValue = this.model.get(this.firstField);
        data.lastValue = this.model.get(this.lastField);
        data.middleValue = this.model.get(this.middleField);
        data.salutationOptions = this.model.getFieldParam(this.salutationField, 'options');

        if (this.isEditMode()) {
            data.firstMaxLength = this.model.getFieldParam(this.firstField, 'maxLength');
            data.lastMaxLength = this.model.getFieldParam(this.lastField, 'maxLength');
            data.middleMaxLength = this.model.getFieldParam(this.middleField, 'maxLength');
        }

        data.valueIsSet = this.model.has(this.firstField) || this.model.has(this.lastField);

        if (this.isDetailMode()) {
            data.isNotEmpty = !!data.firstValue || !!data.lastValue ||
                !!data.salutationValue || !!data.middleValue;
        }
        else if (this.isListMode()) {
            data.isNotEmpty = !!data.firstValue || !!data.lastValue || !!data.middleValue;
        }

        if (
            data.isNotEmpty && this.isDetailMode() ||
            this.isListMode()
        ) {
            data.formattedValue = this.getFormattedValue();
        }

        return data;
    }

    setup() {
        super.setup();

        let ucName = Espo.Utils.upperCaseFirst(this.name);

        this.salutationField = 'salutation' + ucName;
        this.firstField = 'first' + ucName;
        this.lastField = 'last' + ucName;
        this.middleField = 'middle' + ucName;
    }

    afterRender() {
        super.afterRender();

        if (this.isEditMode()) {
            this.$salutation = this.$el.find('[data-name="' + this.salutationField + '"]');
            this.$first = this.$el.find('[data-name="' + this.firstField + '"]');
            this.$last = this.$el.find('[data-name="' + this.lastField + '"]');

            if (this.formatHasMiddle()) {
                this.$middle = this.$el.find('[data-name="' + this.middleField + '"]');
            }

            this.$salutation.on('change', () => {
                this.trigger('change');
            });

            this.$first.on('change', () => {
                this.trigger('change');
            });

            this.$last.on('change', () => {
                this.trigger('change');
            });

            Select.init(this.$salutation);
        }
    }

    getFormattedValue() {
        let salutation = this.model.get(this.salutationField);
        let first = this.model.get(this.firstField);
        let last = this.model.get(this.lastField);
        let middle = this.model.get(this.middleField);

        if (salutation) {
            salutation = this.getLanguage()
                .translateOption(salutation, 'salutationName', this.model.entityType);
        }

        return this.formatName({
            salutation: salutation,
            first: first,
            middle: middle,
            last: last,
        });
    }

    _getTemplateName() {
        if (this.isEditMode()) {
            let prop = 'editTemplate' + Espo.Utils.upperCaseFirst(this.getFormat().toString());

            if (prop in this) {
                return this[prop];
            }
        }

        return super._getTemplateName();
    }

    getFormat() {
        this.format = this.format || this.getConfig().get('personNameFormat') || 'firstLast';

        return this.format;
    }

    formatHasMiddle() {
        let format = this.getFormat();

        return format === 'firstMiddleLast' || format === 'lastFirstMiddle';
    }

    validateRequired() {
        let isRequired = this.isRequired();

        let validate = (name) => {
            if (this.model.isRequired(name)) {
                if (!this.model.get(name)) {
                    let msg = this.translate('fieldIsRequired', 'messages')
                        .replace('{field}', this.translate(name, 'fields', this.model.entityType));
                    this.showValidationMessage(msg, '[data-name="'+name+'"]');

                    return true;
                }
            }
        };

        if (isRequired) {
            if (!this.model.get(this.firstField) && !this.model.get(this.lastField)) {
                let msg = this.translate('fieldIsRequired', 'messages')
                    .replace('{field}', this.getLabelText());

                this.showValidationMessage(msg, '[data-name="'+this.lastField+'"]');

                return true;
            }
        }

        let result = false;

        result = validate(this.salutationField) || result;
        result = validate(this.firstField) || result;
        result = validate(this.lastField) || result;
        result = validate(this.middleField) || result;

        return result;
    }

    validatePattern() {
        let result = false;

        result = this.fieldValidatePattern(this.firstField) || result;
        result = this.fieldValidatePattern(this.lastField) || result;
        result = this.fieldValidatePattern(this.middleField) || result;

        return result;
    }

    hasRequiredMarker() {
        if (this.isRequired()) {
            return true;
        }

        return this.model.getFieldParam(this.salutationField, 'required') ||
            this.model.getFieldParam(this.firstField, 'required') ||
            this.model.getFieldParam(this.middleField, 'required') ||
            this.model.getFieldParam(this.lastField, 'required');
    }

    fetch() {
        let data = {};

        data[this.salutationField] = this.$salutation.val() || null;
        data[this.firstField] = this.$first.val().trim() || null;
        data[this.lastField] = this.$last.val().trim() || null;

        if (this.formatHasMiddle()) {
            data[this.middleField] = this.$middle.val().trim() || null;
        }

        data[this.name] = this.formatName({
            first: data[this.firstField],
            last: data[this.lastField],
            middle: data[this.middleField],
        });

        return data;
    }

    /**
     * @param {{first?: string, last?: string, middle?: string, salutation?: string}}data
     * @return {?string}
     */
    formatName(data) {
        let name;
        let format = this.getFormat();
        let arr = [];

        arr.push(data.salutation);

        if (format === 'firstLast') {
            arr.push(data.first);
            arr.push(data.last);
        }
        else if (format === 'lastFirst') {
            arr.push(data.last);
            arr.push(data.first);
        }
        else if (format === 'firstMiddleLast') {
            arr.push(data.first);
            arr.push(data.middle);
            arr.push(data.last);
        }
        else if (format === 'lastFirstMiddle') {
            arr.push(data.last);
            arr.push(data.first);
            arr.push(data.middle);
        }
        else {
            arr.push(data.first);
            arr.push(data.last);
        }

        name = arr.filter(item => !!item).join(' ').trim();

        if (name === '') {
            name = null;
        }

        return name;
    }
}

export default PersonNameFieldView;
PK]$��,
,
views/fields/bool.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/fields/bool */

import BaseFieldView from 'views/fields/base';
import Select from 'ui/select';

/**
 * A boolean field (checkbox).
 */
class BoolFieldView extends BaseFieldView {

    type = 'bool'

    listTemplate = 'fields/bool/list'
    detailTemplate = 'fields/bool/detail'
    editTemplate = 'fields/bool/edit'
    searchTemplate = 'fields/bool/search'

    validations = []
    initialSearchIsNotIdle = true

    /** @inheritDoc */
    data() {
        let data = super.data();

        data.valueIsSet = this.model.has(this.name);

        return data;
    }

    afterRender() {
        super.afterRender();

        if (this.mode === this.MODE_SEARCH) {
            this.$element.on('change', () => {
                this.trigger('change');
            });

            Select.init(this.$element);
        }
    }

    fetch() {
        let value = this.$element.get(0).checked;

        let data = {};

        data[this.name] = value;

        return data;
    }

    fetchSearch() {
        let type = this.$element.val();

        if (!type) {
            return null;
        }

        if (type === 'any') {
            return {
                type: 'or',
                value: [
                    {
                        type: 'isTrue',
                        attribute: this.name,

                    },
                    {
                        type: 'isFalse',
                        attribute: this.name,
                    },
                ],
                data: {
                    type: type,
                },
            };
        }

        return {
            type: type,
            data: {
                type: type,
            },
        };
    }

    getSearchType() {
        return this.getSearchParamsData().type ||
            this.searchParams.type ||
            'isTrue';
    }
}

export default BoolFieldView;
PK]8�j*�3�3views/fields/varchar.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/fields/varchar */

import BaseFieldView from 'views/fields/base';
import RegExpPattern from 'helpers/reg-exp-pattern';

/**
 * A varchar field.
 */
class VarcharFieldView extends BaseFieldView {

    /**
     * @typedef {Object} module:views/fields/varchar~options
     * @property {
     *     module:views/fields/varchar~params &
     *     module:views/fields/base~params &
     *     Object.<string, *>
     * } [params] Parameters.
     */

    /**
     * @typedef {Object} module:views/fields/varchar~params
     * @property {number} [maxLength] A max length.
     * @property {string[]} [options] Select options.
     * @property {boolean} [required] Required.
     * @property {string} [optionsPath] An options metadata path.
     * @property {boolean} [noSpellCheck] Disable spell check.
     * @property {string} [pattern] A validation pattern. If starts with `$`, then a predefined pattern is used.
     * @property {boolean} [copyToClipboard] To display a Copy-to-clipboard button.
     */

    /**
     * @param {
     *     module:views/fields/varchar~options &
     *     module:views/fields/base~options
     * } options Options.
     */
    constructor(options) {
        super(options);
    }

    type = 'varchar'

    listTemplate = 'fields/varchar/list'
    detailTemplate = 'fields/varchar/detail'
    searchTemplate = 'fields/varchar/search'

    searchTypeList = [
        'startsWith',
        'contains',
        'equals',
        'endsWith',
        'like',
        'notContains',
        'notEquals',
        'notLike',
        'isEmpty',
        'isNotEmpty',
    ]

    /** @inheritDoc */
    validations = [
        'required',
        'pattern',
    ]

    /**
     * Use an autocomplete requesting data from the backend.
     *
     * @protected
     * @type {boolean}
     */
    useAutocompleteUrl = false

    /**
     * No spell-check.
     *
     * @protected
     * @type {boolean}
     */
    noSpellCheck = false

    setup() {
        this.setupOptions();

        this.noSpellCheck = this.noSpellCheck || this.params.noSpellCheck;

        if (this.params.optionsPath) {
            this.params.options = Espo.Utils.clone(
                this.getMetadata().get(this.params.optionsPath) || []);
        }

        if (this.options.customOptionList) {
            this.setOptionList(this.options.customOptionList);
        }

        if (this.mode === this.MODE_DETAIL) {
            if (this.params.copyToClipboard) {
                this.events['click [data-action="copyToClipboard"]'] = () => this.copyToClipboard();
            }
        }
    }

    /**
     * Set up options.
     */
    setupOptions() {}

    /**
     * Set options.
     *
     * @param {string[]} optionList Options.
     */
    setOptionList(optionList) {
        if (!this.originalOptionList) {
            this.originalOptionList = this.params.options || [];
        }

        this.params.options = Espo.Utils.clone(optionList);

        if (this.isEditMode()) {
            if (this.isRendered()) {
                this.reRender();
            }
        }
    }

    /**
     * Reset options.
     */
    resetOptionList() {
        if (this.originalOptionList) {
            this.params.options = Espo.Utils.clone(this.originalOptionList);
        }

        if (this.isEditMode()) {
            if (this.isRendered()) {
                this.reRender();
            }
        }
    }

    /**
     * @protected
     */
    copyToClipboard() {
        let value = this.model.get(this.name);

        navigator.clipboard.writeText(value).then(() => {
            Espo.Ui.success(this.translate('Copied to clipboard'));
        });
    }

    // noinspection JSUnusedLocalSymbols
    /**
     * Compose an autocomplete URL.
     *
     * @param {string} q A query.
     * @return {string}
     */
    getAutocompleteUrl(q) {
        return '';
    }

    transformAutocompleteResult(response) {
        let responseParsed = typeof response === 'string' ?
            JSON.parse(response) :
            response;

        let list = [];

        responseParsed.list.forEach(item => {
            list.push({
                id: item.id,
                name: item.name || item.id,
                data: item.id,
                value: item.name || item.id,
                attributes: item,
            });
        });

        return {
            suggestions: list,
        };
    }

    setupSearch() {
        this.events['change select.search-type'] = e => {
            let type = $(e.currentTarget).val();

            this.handleSearchType(type);
        };
    }

    data() {
        let data = super.data()

        if (
            this.model.get(this.name) !== null &&
            this.model.get(this.name) !== '' &&
            this.model.has(this.name)
        ) {
            data.isNotEmpty = true;
        }

        data.valueIsSet = this.model.has(this.name);

        if (this.isSearchMode()) {
            if (typeof this.searchParams.value === 'string') {
                this.searchData.value = this.searchParams.value;
            }
        }

        data.noSpellCheck = this.noSpellCheck;
        data.copyToClipboard = this.params.copyToClipboard;

        return data;
    }

    handleSearchType(type) {
        if (~['isEmpty', 'isNotEmpty'].indexOf(type)) {
            this.$el.find('input.main-element').addClass('hidden');

            return;
        }

        this.$el.find('input.main-element').removeClass('hidden');
    }

    afterRender() {
        super.afterRender();

        if (this.isSearchMode()) {
            let type = this.$el.find('select.search-type').val();

            this.handleSearchType(type);
        }

        if (
            (this.isEditMode() || this.isSearchMode()) &&
            (
                this.params.options && this.params.options.length ||
                this.useAutocompleteUrl
            )
        ) {
            // noinspection JSUnusedGlobalSymbols
            const autocompleteOptions = {
                minChars: 0,
                lookup: this.params.options,
                maxHeight: 200,
                triggerSelectOnValidInput: false,
                autoSelectFirst: true,
                beforeRender: $c => {
                    if (this.$element.hasClass('input-sm')) {
                        $c.addClass('small');
                    }
                },
                formatResult: suggestion => {
                    return this.getHelper().escapeString(suggestion.value);
                },
                lookupFilter: (suggestion, query, queryLowerCase) => {
                    if (suggestion.value.toLowerCase().indexOf(queryLowerCase) === 0) {
                        return suggestion.value.length !== queryLowerCase.length;
                    }

                    return false;
                },
                onSelect: () => {
                    this.trigger('change');

                    this.$element.focus();
                },
            };

            if (this.useAutocompleteUrl) {
                autocompleteOptions.noCache = true;
                autocompleteOptions.lookup = (query, done) => {
                    Espo.Ajax.getRequest(this.getAutocompleteUrl(query))
                        .then(response => {
                            return this.transformAutocompleteResult(response);
                        })
                        .then(result => {
                            done(result);
                        });
                };
            }

            this.$element.autocomplete(autocompleteOptions);
            this.$element.attr('autocomplete', 'espo-' + this.name);

            // Prevent showing suggestions after select.
            this.$element.off('focus.autocomplete');

            this.$element.on('focus', () => {
                if (this.$element.val()) {
                    return;
                }

                this.$element.autocomplete('onValueChange');
            });

            this.once('render', () => this.$element.autocomplete('dispose'));
            this.once('remove', () => this.$element.autocomplete('dispose'));
        }

        if (this.isSearchMode()) {
            this.$el.find('select.search-type').on('change', () => {
                this.trigger('change');
            });

            this.$element.on('input', () => {
                this.trigger('change');
            });
        }
    }

    // noinspection JSUnusedGlobalSymbols
    validatePattern() {
        let pattern = this.params.pattern;

        return this.fieldValidatePattern(this.name, pattern);
    }

    /**
     * Used by other field views.
     *
     * @param {string} name
     * @param {string} [pattern]
     */
    fieldValidatePattern(name, pattern) {
        pattern = pattern || this.model.getFieldParam(name, 'pattern');
        /** @var {string|null} value */
        let value = this.model.get(name);

        if (!pattern) {
            return false;
        }

        let helper = new RegExpPattern(this.getMetadata(), this.getLanguage());

        let result = helper.validate(pattern, value, name, this.entityType);

        if (!result) {
            return false;
        }

        let message = result.message.replace('{field}', this.getLanguage().translate(this.getLabelText()));

        this.showValidationMessage(message, '[data-name="' + name + '"]');

        return true;
    }

    /** @inheritDoc */
    fetch() {
        let data = {};

        let value = this.$element.val().trim();

        data[this.name] = value || null;

        return data;
    }

    /** @inheritDoc */
    fetchSearch() {
        let type = this.fetchSearchType() || 'startsWith';

        if (~['isEmpty', 'isNotEmpty'].indexOf(type)) {
            if (type === 'isEmpty') {
                return {
                    type: 'or',
                    value: [
                        {
                            type: 'isNull',
                            field: this.name,
                        },
                        {
                            type: 'equals',
                            field: this.name,
                            value: '',
                        },
                    ],
                    data: {
                        type: type,
                    },
                };
            }

            let value = [
                {
                    type: 'isNotNull',
                    field: this.name,
                    value: null,
                },
            ];

            if (!this.model.getFieldParam(this.name, 'notStorable')) {
                value.push({
                    type: 'notEquals',
                    field: this.name,
                    value: '',
                });
            }

            return {
                type: 'and',
                value: value,
                data: {
                    type: type,
                },
            };
        }

        let value = this.$element.val().toString().trim();

        if (!value) {
            return null;
        }

        return {
            value: value,
            type: type,
            data: {
                type: type,
            },
        };
    }

    getSearchType() {
        return this.getSearchParamsData().type || this.searchParams.typeFront ||
            this.searchParams.type;
    }
}

export default VarcharFieldView;
PK]�views/fields/enum-float.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import EnumIntFieldView from 'views/fields/enum-int';

class EnumFloatFieldView extends EnumIntFieldView {

    type = 'enumFloat'

    fetch() {
        let value = parseFloat(this.$element.val());
        let data = {};

        data[this.name] = value;

        return data;
    }

    parseItemForSearch(item) {
        return parseFloat(item);
    }
}

export default EnumFloatFieldView;
PK]8h���(�("views/fields/complex-expression.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import TextFieldView from 'views/fields/text';

/**
 * @type {{
 *     edit: import('ace-builds').edit,
 *     require: import('ace-builds').require,
 * }}
 */
let ace;

class ComplexExpressionFieldView extends TextFieldView {

    detailTemplate = 'fields/formula/detail'
    editTemplate = 'fields/formula/edit'

    height = 50
    maxLineDetailCount = 80
    maxLineEditCount = 200

    events = {
        /** @this ComplexExpressionFieldView */
        'click [data-action="addAttribute"]': function () {
            this.addAttribute();
        },
        /** @this ComplexExpressionFieldView */
        'click [data-action="addFunction"]': function () {
            this.addFunction();
        },
    }

    setup() {
        super.setup();

        this.height = this.options.height || this.params.height || this.height;

        this.maxLineDetailCount =
            this.options.maxLineDetailCount ||
            this.params.maxLineDetailCount ||
            this.maxLineDetailCount;

        this.maxLineEditCount =
            this.options.maxLineEditCount ||
            this.params.maxLineEditCount ||
            this.maxLineEditCount;

        this.targetEntityType =
            this.options.targetEntityType ||
            this.params.targetEntityType ||
            this.targetEntityType;

        this.containerId = 'editor-' + Math.floor((Math.random() * 10000) + 1).toString();

        if (this.mode === this.MODE_EDIT || this.mode === this.MODE_DETAIL) {
            this.wait(
                this.requireAce()
            );
        }

        this.on('remove', () => {
            if (this.editor) {
                this.editor.destroy();
            }
        });
    }

    requireAce() {
        return Espo.loader.requirePromise('lib!ace')
            .then(lib => {
                ace = lib;

                let list = [
                    Espo.loader.requirePromise('lib!ace-ext-language_tools'),
                ];

                if (this.getThemeManager().getParam('isDark')) {
                    list.push(
                        Espo.loader.requirePromise('lib!ace-theme-tomorrow_night')
                    );
                }

                return Promise.all(list);
            });
    }

    data() {
        let data = super.data();

        data.containerId = this.containerId;
        data.targetEntityType = this.targetEntityType;
        data.hasInsert = true;

        return data;
    }

    afterRender() {
        super.afterRender();

        this.$editor = this.$el.find('#' + this.containerId);

        if (
            this.$editor.length &&
            (
                this.mode === this.MODE_EDIT ||
                this.mode === this.MODE_DETAIL ||
                this.mode === this.MODE_LIST
            )
        ) {
            this.$editor.css('fontSize', '14px');

            if (this.mode === this.MODE_EDIT) {
                this.$editor.css('minHeight', this.height + 'px');
            }

            const editor = this.editor = ace.edit(this.containerId);

            editor.setOptions({
                maxLines: this.mode === this.MODE_EDIT ? this.maxLineEditCount : this.maxLineDetailCount,
            });

            if (this.getThemeManager().getParam('isDark')) {
                editor.setOptions({
                    theme: 'ace/theme/tomorrow_night',
                });
            }

            if (this.isEditMode()) {
                editor.getSession().on('change', () => {
                    this.trigger('change', {ui: true});
                });

                editor.getSession().setUseWrapMode(true);
            }

            if (this.isReadMode()) {
                editor.setReadOnly(true);
                editor.renderer.$cursorLayer.element.style.display = 'none';
                editor.renderer.setShowGutter(false);
            }

            editor.setShowPrintMargin(false);
            editor.getSession().setUseWorker(false);
            editor.commands.removeCommand('find');
            editor.setHighlightActiveLine(false);

            //let JavaScriptMode = ace.require('ace/mode/javascript').Mode;
            //editor.session.setMode(new JavaScriptMode());

            if (!this.isReadMode()) {
                this.initAutocomplete();
            }
        }
    }

    fetch() {
        let data = {};

        data[this.name] = this.editor.getValue();

        return data;
    }

    getFunctionDataList() {
        return this.getMetadata().get(['app', 'complexExpression', 'functionList']) || [];
    }

    initAutocomplete() {
        let functionItemList =
            this.getFunctionDataList()
                .filter(item => {
                    return item.insertText;
                });

        let attributeList = this.getFormulaAttributeList();

        ace.require('ace/ext/language_tools');

        this.editor.setOptions({
            enableBasicAutocompletion: true,
            enableLiveAutocompletion: true,
        });

        // noinspection JSUnusedGlobalSymbols
        const completer = {
            identifierRegexps: [/[\\a-zA-Z0-9{}\[\].$'"]/],

            getCompletions: function (editor, session, pos, prefix, callback) {
                let matchedFunctionItemList = functionItemList
                    .filter(originalItem => {
                        let text = originalItem.name.toLowerCase();

                        if (text.indexOf(prefix.toLowerCase()) === 0) {
                            return true;
                        }

                        return false;
                    });

                let itemList = matchedFunctionItemList.map(item => {
                    return {
                        caption: item.name + '()',
                        value: item.insertText,
                        meta: item.returnType || null,
                    };
                });

                let matchedAttributeList = attributeList.filter(item => {
                    if (item.indexOf(prefix) === 0) {
                        return true;
                    }

                    return false;
                });

                let itemAttributeList = matchedAttributeList.map((item) => {
                    return {
                        name: item,
                        value: item,
                        meta: 'attribute',
                    };
                });

                itemList = itemList.concat(itemAttributeList);

                callback(null, itemList);
            }
        };

        this.editor.completers = [completer];
    }

    getFormulaAttributeList() {
        if (!this.targetEntityType) {
            return [];
        }

        let attributeList = this.getFieldManager()
            .getEntityTypeAttributeList(this.targetEntityType)
            .sort();

        attributeList.unshift('id');

        // @todo Skip not storable attributes.

        let links = this.getMetadata().get(['entityDefs', this.targetEntityType, 'links']) || {};

        let linkList = [];

        Object.keys(links).forEach(link => {
            let type = links[link].type;

            if (!type) {
                return;
            }

            if (~['hasMany', 'hasOne', 'belongsTo'].indexOf(type)) {
                linkList.push(link);
            }
        });

        linkList.sort();

        linkList.forEach(link => {
            let scope = links[link].entity;

            if (!scope) {
                return;
            }

            if (links[link].disabled) {
                return;
            }

            let linkAttributeList = this.getFieldManager()
                .getEntityTypeAttributeList(scope)
                .sort();

            linkAttributeList.forEach(item => {
                attributeList.push(link + '.' + item);
            });
        });

        return attributeList;
    }

    addAttribute() {
        this.createView('dialog', 'views/admin/formula/modals/add-attribute', {
            scope: this.targetEntityType,
            attributeList: this.getFormulaAttributeList(),
        }, view => {
            view.render();

            this.listenToOnce(view, 'add', attribute => {
                this.editor.insert(attribute);

                this.clearView('dialog');
            });
        });
    }

    addFunction() {
        this.createView('dialog', 'views/admin/complex-expression/modals/add-function', {
            scope: this.targetEntityType,
            functionDataList: this.getFunctionDataList(),
        }, view => {
            view.render();

            this.listenToOnce(view, 'add', string => {
                this.editor.insert(string);

                this.clearView('dialog');
            });
        });
    }
}

export default ComplexExpressionFieldView;
PK]�3�RL	L	views/fields/email-address.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import VarcharFieldView from 'views/fields/varchar';

class EmailAddressFieldView extends VarcharFieldView {

    editTemplate = 'fields/email-address/edit'

    validations = ['required', 'emailAddress']

    emailAddressRe = new RegExp(
        /^[-!#$%&'*+/=?^_`{|}~A-Za-z0-9]+(?:\.[-!#$%&'*+/=?^_`{|}~A-Za-z0-9]+)*/.source +
        /@([A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?\.)+[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9]/.source
    )

    // noinspection JSUnusedGlobalSymbols
    validateEmailAddress() {
        const value = this.model.get(this.name);

        if (!value) {
            return false;
        }

        if (value !== '' && !this.emailAddressRe.test(value)) {
            const msg = this.translate('fieldShouldBeEmail', 'messages')
                .replace('{field}', this.getLabelText());

            this.showValidationMessage(msg);

            return true;
        }

        return false;
    }
}

export default EmailAddressFieldView;
PK]MZN�\\views/fields/foreign-varchar.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import VarcharFieldView from 'views/fields/varchar';
import Helper from 'helpers/misc/foreign-field';

class ForeignVarcharFieldView extends VarcharFieldView {

    type = 'foreign'

    setup() {
        super.setup();

        const helper = new Helper(this);

        const foreignParams = helper.getForeignParams();

        for (let param in foreignParams) {
            this.params[param] = foreignParams[param];
        }
    }
}

export default ForeignVarcharFieldView;
PK]�"��views/fields/complex-created.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import BaseFieldView from 'views/fields/base';

class ComplexCreatedFieldView extends BaseFieldView {

    // language=Handlebars
    detailTemplateContent =
        `<span data-name="{{baseName}}At" class="field">{{{atField}}}</span> ` +
        `<span class="text-muted chevron-right"</span> ` +
        `<span data-name="{{baseName}}By" class="field">{{{byField}}}</span>`

    baseName = 'created'

    getAttributeList() {
        return [this.fieldAt, this.fieldBy];
    }

    init() {
        this.baseName = this.options.baseName || this.baseName;
        this.fieldAt = this.baseName + 'At';
        this.fieldBy = this.baseName + 'By';

        super.init();
    }

    setup() {
        super.setup();

        this.createField('at');
        this.createField('by');
    }

    data() {
        return {
            baseName: this.baseName,
            ...super.data(),
        };
    }

    createField(part) {
        let field = this.baseName + Espo.Utils.upperCaseFirst(part);

        let type = this.model.getFieldType(field) || 'base';

        let viewName = this.model.getFieldParam(field, 'view') ||
            this.getFieldManager().getViewName(type);

        this.createView(part + 'Field', viewName, {
            name: field,
            model: this.model,
            mode: this.MODE_DETAIL,
            readOnly: true,
            readOnlyLocked: true,
            selector: '[data-name="' + field + '"]',
        });
    }

    fetch() {
        return {};
    }
}

export default ComplexCreatedFieldView;
PK]@Y��JPJPviews/fields/email.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import VarcharFieldView from 'views/fields/varchar';

class EmailFieldView extends VarcharFieldView {

    type = 'email'

    editTemplate = 'fields/email/edit'
    detailTemplate = 'fields/email/detail'
    listTemplate = 'fields/email/list'

    validations = ['required', 'emailData']

    events = {
        /** @this EmailFieldView */
        'click [data-action="mailTo"]': function (e) {
            this.mailTo($(e.currentTarget).data('email-address'));
        },
        /** @this EmailFieldView */
        'click [data-action="switchEmailProperty"]': function (e) {
            let $target = $(e.currentTarget);
            let $block = $(e.currentTarget).closest('div.email-address-block');
            let property = $target.data('property-type');

            if (property === 'primary') {
                if (!$target.hasClass('active')) {
                    if ($block.find('input.email-address').val() !== '') {
                        this.$el.find('button.email-property[data-property-type="primary"]')
                            .removeClass('active').children().addClass('text-muted');

                        $target.addClass('active').children().removeClass('text-muted');
                    }
                }
            } else {
                if ($target.hasClass('active')) {
                    $target.removeClass('active').children().addClass('text-muted');
                } else {
                    $target.addClass('active').children().removeClass('text-muted');
                }
            }

            this.trigger('change');
        },
        /** @this EmailFieldView */
        'click [data-action="removeEmailAddress"]': function (e) {
            let $block = $(e.currentTarget).closest('div.email-address-block');

            this.removeEmailAddress($block);

            let $last = this.$el.find('.email-address').last();

            if ($last.length) {
                $last[0].focus({preventScroll: true});
            }
        },
        /** @this EmailFieldView */
        'change input.email-address': function (e) {
            let $input = $(e.currentTarget);
            let $block = $input.closest('div.email-address-block');

            if (this._itemJustRemoved) {
                return;
            }

            if ($input.val() === '' && $block.length) {
                this.removeEmailAddress($block);
            }
            else {
                this.trigger('change');
            }

            this.trigger('change');

            this.manageAddButton();
        },
        /** @this EmailFieldView */
        'keypress input.email-address': function () {
            this.manageAddButton();
        },
        /** @this EmailFieldView */
        'paste input.email-address': function () {
            setTimeout(() => this.manageAddButton(), 10);
        },
        /** @this EmailFieldView */
        'click [data-action="addEmailAddress"]': function () {
            this.addEmailAddress();
        },
        /** @this EmailFieldView */
        'keydown input.email-address': function (e) {
            let key = Espo.Utils.getKeyFromKeyEvent(e);

            let $target = $(e.currentTarget);

            if (key === 'Enter') {
                if (!this.$el.find('[data-action="addEmailAddress"]').hasClass('disabled')) {
                    this.addEmailAddress();

                    e.stopPropagation();
                }

                return;
            }

            if (key === 'Backspace' && $target.val() === '') {
                let $block = $target.closest('div.email-address-block');

                this._itemJustRemoved = true;
                setTimeout(() => this._itemJustRemoved = false, 100);

                e.stopPropagation();

                this.removeEmailAddress($block);

                setTimeout(() => this.focusOnLast(true), 50);
            }
        },
    }

    validateEmailData() {
        let data = this.model.get(this.dataFieldName);

        if (!data || !data.length) {
            return;
        }

        let addressList = [];

        let regExp = new RegExp(
            /^[-!#$%&'*+/=?^_`{|}~A-Za-z0-9]+(?:\.[-!#$%&'*+/=?^_`{|}~A-Za-z0-9]+)*/.source +
            /@([A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?\.)+[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9]/.source
        );

        let notValid = false;

        data.forEach((row, i) => {
            let address = row.emailAddress || '';
            let addressLowerCase = String(address).toLowerCase();

            if (!regExp.test(addressLowerCase) && address.indexOf(this.erasedPlaceholder) !== 0) {
                let msg = this.translate('fieldShouldBeEmail', 'messages')
                    .replace('{field}', this.getLabelText());

                this.reRender();

                this.showValidationMessage(msg, 'div.email-address-block:nth-child(' + (i + 1)
                    .toString() + ') input');

                notValid = true;

                return;
            }

            if (~addressList.indexOf(addressLowerCase)) {
                let msg = this.translate('fieldValueDuplicate', 'messages')
                    .replace('{field}', this.getLabelText());

                this.showValidationMessage(msg, 'div.email-address-block:nth-child(' + (i + 1)
                    .toString() + ') input');

                notValid = true;

                return;
            }

            addressList.push(addressLowerCase);
        });

        if (notValid) {
            return true;
        }
    }

    validateRequired() {
        if (this.isRequired()) {
            if (!this.model.get(this.name)) {
                let msg = this.translate('fieldIsRequired', 'messages')
                    .replace('{field}', this.getLabelText());

                this.showValidationMessage(msg, 'div.email-address-block:nth-child(1) input');

                return true;
            }
        }
    }

    data() {
        let emailAddressData;

        if (this.mode === this.MODE_EDIT) {
            emailAddressData = Espo.Utils.clone(this.model.get(this.dataFieldName));

            if (this.model.isNew() || !this.model.get(this.name)) {
                if (!emailAddressData || !emailAddressData.length) {
                    let optOut;

                    if (this.model.isNew()) {
                        optOut = this.emailAddressOptedOutByDefault && this.model.entityType !== 'User';
                    } else {
                        optOut = this.model.get(this.isOptedOutFieldName)
                    }

                    emailAddressData = [{
                        emailAddress: this.model.get(this.name) || '',
                        primary: true,
                        optOut: optOut,
                        invalid: false,
                    }];
                }
            }
        } else {
            emailAddressData = this.model.get(this.dataFieldName) || false;
        }

        if ((!emailAddressData || emailAddressData.length === 0) && this.model.get(this.name)) {
            emailAddressData = [{
                emailAddress: this.model.get(this.name),
                primary: true,
                optOut: false,
                invalid: false,
            }];
        }

        if (emailAddressData) {
            emailAddressData = Espo.Utils.cloneDeep(emailAddressData);

            emailAddressData.forEach(item => {
                let address = item.emailAddress || '';

                item.erased = address.indexOf(this.erasedPlaceholder) === 0;
                item.lineThrough = item.optOut || item.invalid;
            });
        }

        let data = {
            ...super.data(),
            emailAddressData: emailAddressData,
        };

        if (this.isReadMode()) {
            data.isOptedOut = this.model.get(this.isOptedOutFieldName);
            data.isInvalid = this.model.get(this.isInvalidFieldName);

            if (this.model.get(this.name)) {
                data.isErased = this.model.get(this.name).indexOf(this.erasedPlaceholder) === 0
            }

            data.valueIsSet = this.model.has(this.name);
        }

        data.itemMaxLength = this.itemMaxLength;

        return data;
    }

    getAutocompleteMaxCount() {
        if (this.autocompleteMaxCount) {
            return this.autocompleteMaxCount;
        }

        return this.getConfig().get('recordsPerPage');
    }



    focusOnLast(cursorAtEnd) {
        let $item = this.$el.find('input.form-control').last();

        $item.focus();

        if (cursorAtEnd && $item[0]) {
            // Not supported for email inputs.
            // $item[0].setSelectionRange($item[0].value.length, $item[0].value.length);
        }
    }

    removeEmailAddress($block) {
        if ($block.parent().children().length === 1) {
            $block.find('input.email-address').val('');
        } else {
            this.removeEmailAddressBlock($block);
        }

        this.trigger('change');
    }

    addEmailAddress() {
        let data = Espo.Utils.cloneDeep(this.fetchEmailAddressData());

        let o = {
            emailAddress: '',
            primary: !data.length,
            optOut: this.emailAddressOptedOutByDefault,
            invalid: false,
            lower: '',
        };

        data.push(o);

        this.model.set(this.dataFieldName, data, {silent: true});

        this.reRender()
            .then(() => this.focusOnLast());
    }

    removeEmailAddressBlock($block) {
        let changePrimary = false;

        if ($block.find('button[data-property-type="primary"]').hasClass('active')) {
            changePrimary = true;
        }

        $block.remove();

        if (changePrimary) {
            this.$el.find('button[data-property-type="primary"]')
                .first().addClass('active').children().removeClass('text-muted');
        }

        this.manageButtonsVisibility();
        this.manageAddButton();
    }

    afterRender() {
        super.afterRender();

        this.manageButtonsVisibility();
        this.manageAddButton();

        if (this.mode === this.MODE_SEARCH && this.getAcl().check('Email', 'create')) {
            this.$element.autocomplete({
                serviceUrl: () => {
                    return `EmailAddress/search` +
                        `?maxSize=${this.getAutocompleteMaxCount()}`
                },
                paramName: 'q',
                minChars: 1,
                autoSelectFirst: true,
                triggerSelectOnValidInput: false,
                formatResult: (suggestion) => {
                    return this.getHelper().escapeString(suggestion.name) + ' &#60;' +
                        this.getHelper().escapeString(suggestion.id) + '&#62;';
                },
                transformResult: (response) => {
                    response = JSON.parse(response);
                    let list = [];

                    response.forEach(item => {
                        list.push({
                            id: item.emailAddress,
                            name: item.entityName,
                            emailAddress: item.emailAddress,
                            entityId: item.entityId,
                            entityName: item.entityName,
                            entityType: item.entityType,
                            data: item.emailAddress,
                            value: item.emailAddress,
                        });
                    });

                    return {suggestions: list};
                },
                onSelect: (s) => {
                    this.$element.val(s.emailAddress);
                    this.$element.focus();
                },
            });
        }
    }

    manageAddButton() {
        let $input = this.$el.find('input.email-address');
        let c = 0;

        $input.each((i, input) => {
            if (input.value !== '') {
                c++;
            }
        });

        if (c === $input.length) {
            this.$el.find('[data-action="addEmailAddress"]')
                .removeClass('disabled')
                .removeAttr('disabled');

            return;
        }

        this.$el.find('[data-action="addEmailAddress"]')
            .addClass('disabled')
            .attr('disabled', 'disabled');
    }

    manageButtonsVisibility() {
        let $primary = this.$el.find('button[data-property-type="primary"]');
        let $remove = this.$el.find('button[data-action="removeEmailAddress"]');

        if ($primary.length > 1) {
            $primary.removeClass('hidden');
            $remove.removeClass('hidden');
        } else {
            $primary.addClass('hidden');
            $remove.addClass('hidden');
        }
    }

    mailTo(emailAddress) {
        let attributes = {
            status: 'Draft',
            to: emailAddress
        };

        let scope = this.model.entityType;

        switch (scope) {
            case 'Account':
            case 'Lead':
                attributes.parentType = scope;
                attributes.parentName = this.model.get('name');
                attributes.parentId = this.model.id;
                break;
            case 'Contact':
                if (this.getConfig().get('b2cMode')) {
                    attributes.parentType = 'Contact';
                    attributes.parentName = this.model.get('name');
                    attributes.parentId = this.model.id;
                } else {
                    if (this.model.get('accountId')) {
                        attributes.parentType = 'Account';
                        attributes.parentName = this.model.get('accountName');
                        attributes.parentId = this.model.get('accountId');
                    }
                }
                break;
        }

        if (this.model.collection && this.model.collection.parentModel) {
            if (this.checkParentTypeAvailability(this.model.collection.parentModel.entityType)) {
                attributes.parentType = this.model.collection.parentModel.entityType;
                attributes.parentId = this.model.collection.parentModel.id;
                attributes.parentName = this.model.collection.parentModel.get('name');
            }
        }

        if (!attributes.parentId) {
            if (this.checkParentTypeAvailability(this.model.entityType)) {
                attributes.parentType = this.model.entityType;
                attributes.parentId = this.model.id;
                attributes.parentName = this.model.get('name');
            }
        } else {
            if (attributes.parentType && !this.checkParentTypeAvailability(attributes.parentType)) {
                attributes.parentType = null;
                attributes.parentId = null;
                attributes.parentName = null;
            }
        }


        if (~['Contact', 'Lead', 'Account'].indexOf(this.model.entityType)) {
            attributes.nameHash = {};
            attributes.nameHash[emailAddress] = this.model.get('name');
        }

        if (
            this.getConfig().get('emailForceUseExternalClient') ||
            this.getPreferences().get('emailUseExternalClient') ||
            !this.getAcl().checkScope('Email', 'create')
        ) {
            Espo.loader.require('email-helper', EmailHelper => {
                let emailHelper = new EmailHelper();

                document.location.href = emailHelper
                    .composeMailToLink(attributes, this.getConfig().get('outboundEmailBccAddress'));
            });

            return;
        }

        let viewName = this.getMetadata()
            .get('clientDefs.' + this.scope + '.modalViews.compose') || 'views/modals/compose-email';

        Espo.Ui.notify(' ... ');

        this.createView('quickCreate', viewName, {
            attributes: attributes,
        }, view => {
            view.render();
            view.notify(false);
        });
    }

    checkParentTypeAvailability(parentType) {
        return ~(this.getMetadata()
            .get(['entityDefs', 'Email', 'fields', 'parent', 'entityList']) || []).indexOf(parentType);
    }

    setup() {
        this.dataFieldName = this.name + 'Data';
        this.isOptedOutFieldName = this.name + 'IsOptedOut';
        this.isInvalidFieldName = this.name + 'IsInvalid';

        this.erasedPlaceholder = 'ERASED:';

        this.emailAddressOptedOutByDefault = this.getConfig().get('emailAddressIsOptedOutByDefault');

        this.itemMaxLength = this.getMetadata()
            .get(['entityDefs', 'EmailAddress', 'fields', 'name', 'maxLength']) || 255;
    }

    fetchEmailAddressData() {
        let data = [];

        let $list = this.$el.find('div.email-address-block');

        if ($list.length) {
            $list.each((i, d) => {
                let row = {};
                let $d = $(d);

                row.emailAddress = $d.find('input.email-address').val().trim();

                if (row.emailAddress === '') {
                    return;
                }

                row.primary = $d.find('button[data-property-type="primary"]').hasClass('active');
                row.optOut = $d.find('button[data-property-type="optOut"]').hasClass('active');
                row.invalid = $d.find('button[data-property-type="invalid"]').hasClass('active');
                row.lower = row.emailAddress.toLowerCase();

                data.push(row);
            });
        }

        return data;
    }


    fetch() {
        let data = {};

        let addressData = this.fetchEmailAddressData() || [];

        data[this.dataFieldName] = addressData;
        data[this.name] = null;
        data[this.isOptedOutFieldName] = false;
        data[this.isInvalidFieldName] = false;

        let primaryIndex = 0;

        addressData.forEach((item, i) => {
            if (item.primary) {
                primaryIndex = i;

                if (item.optOut) {
                    data[this.isOptedOutFieldName] = true;
                }

                if (item.invalid) {
                    data[this.isInvalidFieldName] = true;
                }
            }
        });

        if (addressData.length && primaryIndex > 0) {
            let t = addressData[0];

            addressData[0] = addressData[primaryIndex];
            addressData[primaryIndex] = t;
        }

        if (addressData.length) {
            data[this.name] = addressData[0].emailAddress;
        } else {
            data[this.isOptedOutFieldName] = null;
            data[this.isInvalidFieldName] = null;
        }

        return data;
    }
}

export default EmailFieldView;
PK];6>��	�	views/fields/array-int.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ArrayFieldView from 'views/fields/array';

class ArrayIntFieldView extends ArrayFieldView {

    type = 'arrayInt'

    fetchFromDom() {
        let selected = [];

        this.$el.find('.list-group .list-group-item').each((i, el) => {
            let value = $(el).data('value');

            if (typeof value === 'string' || value instanceof String) {
                value = parseInt($(el).data('value'));
            }

            selected.push(value);
        });

        this.selected = selected;
    }

    addValue(value) {
        value = parseInt(value);

        if (isNaN(value)) {
            return;
        }

        super.addValue(value);
    }

    removeValue(value) {
        value = parseInt(value);

        if (isNaN(value)) {
            return;
        }

        let valueInternal = value.toString().replace(/"/g, '\\"');

        this.$list.children('[data-value="' + valueInternal + '"]').remove();

        let index = this.selected.indexOf(value);

        this.selected.splice(index, 1);
        this.trigger('change');
    }
}

export default ArrayIntFieldView;
PK]g
�ڿ�views/fields/enum-column.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import EnumFieldView from 'views/fields/enum';

class EnumColumnFieldView extends EnumFieldView {

    searchTypeList = ['anyOf', 'noneOf']

    fetchSearch() {
        let type = this.fetchSearchType();

        let list = this.$element.val().split(':,:');

        if (list.length === 1 && list[0] === '') {
            list = [];
        }

        list.forEach((item, i) => {
            list[i] = this.parseItemForSearch(item);
        });

        if (type === 'anyOf') {
            if (list.length === 0) {
                return {
                    data: {
                        type: 'anyOf',
                        valueList: list,
                    },
                };
            }

            return {
                type: 'columnIn',
                value: list,
                data: {
                    type: 'anyOf',
                    valueList: list,
                },
            };
        }
        else if (type === 'noneOf') {
            if (list.length === 0) {
                return {
                    data: {
                        type: 'noneOf',
                        valueList: list,
                    },
                };
            }

            return {
                type: 'or',
                value: [
                    {
                        type: 'columnIsNull',
                        attribute: this.name,
                    },
                    {
                        type: 'columnNotIn',
                        value: list,
                        attribute: this.name,
                    }
                ],
                data: {
                    type: 'noneOf',
                    valueList: list,
                },
            };
        }

        return null;
    }
}

export default EnumColumnFieldView;
PK]'+���*views/fields/link-multiple-with-primary.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import LinkMultipleFieldView from 'views/fields/link-multiple';

/**
 * A link-multiple field with a primary.
 */
class LinkMultipleWithPrimaryFieldView extends LinkMultipleFieldView {

    /**
     * @protected
     * @type {string}
     */
    primaryLink

    switchPrimary(id) {
        let $switch = this.$el.find(`[data-id="${id}"][data-action="switchPrimary"]`);

        if (!$switch.hasClass('active')) {
            this.$el.find('button[data-action="switchPrimary"]')
                .removeClass('active')
                .children()
                .addClass('text-muted');

            $switch.addClass('active').children().removeClass('text-muted');

            this.setPrimaryId(id);
        }
    }

    /**
     * @inheritDoc
     */
    getAttributeList() {
        const list = super.getAttributeList();

        list.push(this.primaryIdAttribute);
        list.push(this.primaryNameAttribute);

        return list;
    }

    setup() {
        this.primaryLink = this.options.primaryLink || this.primaryLink ||
            this.model.getFieldParam(this.name, 'primaryLink');

        this.primaryIdAttribute = this.primaryLink + 'Id';
        this.primaryNameAttribute = this.primaryLink + 'Name';

        super.setup();

        this.primaryId = this.model.get(this.primaryIdAttribute);
        this.primaryName = this.model.get(this.primaryNameAttribute);

        this.listenTo(this.model, 'change:' + this.primaryIdAttribute, () => {
            this.primaryId = this.model.get(this.primaryIdAttribute);
            this.primaryName = this.model.get(this.primaryNameAttribute);
        });

        this.events['click [data-action="switchPrimary"]'] = e => {
            let $target = $(e.currentTarget);
            let id = $target.data('id');

            this.switchPrimary(id);
        };
    }

    /**
     * @protected
     * @param {string|null} id An ID.
     */
    setPrimaryId(id) {
        this.primaryId = id;

        this.primaryName = id ?
            this.nameHash[id] : null;

        this.trigger('change');
    }

    /**
     * @protected
     */
    renderLinks() {
        if (this.primaryId) {
            this.addLinkHtml(this.primaryId, this.primaryName);
        }

        this.ids.forEach(id => {
            if (id !== this.primaryId) {
                this.addLinkHtml(id, this.nameHash[id]);
            }
        });
    }

    /**
     * @inheritDoc
     */
    getValueForDisplay() {
        if (this.isDetailMode() || this.isListMode()) {
            let itemList = [];

            if (this.primaryId) {
                itemList.push(this.getDetailLinkHtml(this.primaryId, this.primaryName));
            }

            if (!this.ids.length) {
                return;
            }

            this.ids.forEach(id => {
                if (id !== this.primaryId) {
                    itemList.push(this.getDetailLinkHtml(id));
                }
            });

            return itemList
                .map(item => $('<div>').append(item).get(0).outerHTML)
                .join('');
        }
    }

    /**
     * @inheritDoc
     */
    deleteLink(id) {
        if (id === this.primaryId) {
            this.setPrimaryId(null);
        }

        super.deleteLink(id);
    }

    /**
     * @inheritDoc
     */
    deleteLinkHtml(id) {
        super.deleteLinkHtml(id);

        this.managePrimaryButton();
    }

    /**
     * @inheritDoc
     */
    addLinkHtml(id, name) {
        // Do not use the `html` method to avoid XSS.

        name = name || id;

        if (this.isSearchMode()) {
            return super.addLinkHtml(id, name);
        }

        let $container = this.$el.find('.link-container');

        let $el = $('<div>')
            .addClass('form-inline clearfix ')
            .addClass('list-group-item link-with-role link-group-item-with-primary')
            .addClass('link-' + id)
            .attr('data-id', id);

        let $name = $('<div>').text(name).append('&nbsp;');

        let $remove = $('<a>')
            .attr('role', 'button')
            .attr('tabindex', '0')
            .attr('data-id', id)
            .attr('data-action', 'clearLink')
            .addClass('pull-right')
            .append(
                $('<span>').addClass('fas fa-times')
            );

        let $left = $('<div>');
        let $right = $('<div>');

        $left.append($name);
        $right.append($remove);

        $el.append($left);
        $el.append($right);

        let isPrimary = (id === this.primaryId);

        let $star = $('<span>')
            .addClass('fas fa-star fa-sm')
            .addClass(!isPrimary ? 'text-muted' : '')

        let $button = $('<button>')
            .attr('type', 'button')
            .addClass('btn btn-link btn-sm pull-right hidden')
            .attr('title', this.translate('Primary'))
            .attr('data-action', 'switchPrimary')
            .attr('data-id', id)
            .append($star);

        $button.insertBefore($el.children().first().children().first());

        $container.append($el);

        this.managePrimaryButton();

        return $el;
    }

    /**
     * @protected
     */
    managePrimaryButton() {
        let $primary = this.$el.find('button[data-action="switchPrimary"]');

        if ($primary.length > 1) {
            $primary.removeClass('hidden');
        }
        else {
            $primary.addClass('hidden');
        }

        if ($primary.filter('.active').length === 0) {
            let $first = $primary.first();

            if ($first.length) {
                $first.addClass('active').children().removeClass('text-muted');

                this.setPrimaryId($first.data('id'));
            }
        }
    }

    fetch() {
        const data = super.fetch();

        data[this.primaryIdAttribute] = this.primaryId;
        data[this.primaryNameAttribute] = this.primaryName;

        // noinspection JSValidateTypes
        return data;
    }
}

export default LinkMultipleWithPrimaryFieldView;
PK]
�߁MMviews/fields/foreign-text.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import TextFieldView from 'views/fields/text';
import Helper from 'helpers/misc/foreign-field';

class ForeignTextFieldView extends TextFieldView {

    type = 'foreign'

    setup() {
        super.setup();

        const helper = new Helper(this);

        const foreignParams = helper.getForeignParams();

        for (let param in foreignParams) {
            this.params[param] = foreignParams[param];
        }
    }
}

export default ForeignTextFieldView;
PK]o ���views/fields/address-country.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import VarcharFieldView from 'views/fields/varchar';

class AddressCountryFieldView extends VarcharFieldView {

    setupOptions() {
        let countryList = this.getConfig().get('addressCountryList') || [];

        if (countryList.length) {
            this.params.options = Espo.Utils.clone(countryList);
        }
    }
}

export default AddressCountryFieldView;
PK]�<e�,,views/fields/foreign-phone.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import PhoneFieldView from 'views/fields/phone';

class ForeignPhoneFieldView extends PhoneFieldView {

    type = 'foreign'
    readOnly = true
}

export default ForeignPhoneFieldView;
PK]Z���&�&views/fields/range-int.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import BaseFieldView from 'views/fields/base';
import IntFieldView from 'views/fields/int';
import AutoNumeric from 'autonumeric';

class RangeIntFieldView extends BaseFieldView {

    type = 'rangeInt'

    listTemplate = 'fields/range-int/detail'
    detailTemplate = 'fields/range-int/detail'
    editTemplate = 'fields/range-int/edit'

    validations = ['required', 'int', 'range', 'order']

    // noinspection JSCheckFunctionSignatures
    data() {
        const data = super.data();

        data.ucName = Espo.Utils.upperCaseFirst(this.name);
        data.fromValue = this.model.get(this.fromField);
        data.toValue = this.model.get(this.toField);

        // noinspection JSValidateTypes
        return data;
    }

    init() {
        const ucName = Espo.Utils.upperCaseFirst(this.options.defs.name);

        this.fromField = 'from' + ucName;
        this.toField = 'to' + ucName;

        super.init();
    }

    getValueForDisplay() {
        let fromValue = this.model.get(this.fromField);
        let toValue = this.model.get(this.toField);

        fromValue = isNaN(fromValue) ? null : fromValue;
        toValue = isNaN(toValue) ? null : toValue;

        if (fromValue !== null && toValue !== null) {
            return this.formatNumber(fromValue) + ' &#8211 ' + this.formatNumber(toValue);
        }
        else if (fromValue) {
            return '&#62;&#61; ' + this.formatNumber(fromValue);
        }
        else if (toValue) {
            return '&#60;&#61; ' + this.formatNumber(toValue);
        }

        return this.translate('None');
    }

    setup() {
        if (this.getPreferences().has('decimalMark')) {
            this.decimalMark = this.getPreferences().get('decimalMark');
        }
        else {
            if (this.getConfig().has('decimalMark')) {
                this.decimalMark = this.getConfig().get('decimalMark');
            }
        }

        if (this.getPreferences().has('thousandSeparator')) {
            this.thousandSeparator = this.getPreferences().get('thousandSeparator');
        }
        else {
            if (this.getConfig().has('thousandSeparator')) {
                this.thousandSeparator = this.getConfig().get('thousandSeparator');
            }
        }
    }

    setupFinal() {
        super.setupFinal();

        this.setupAutoNumericOptions();
    }

    /**
     * @protected
     */
    setupAutoNumericOptions() {
        let separator = (!this.disableFormatting ? this.thousandSeparator : null) || '';
        let decimalCharacter = '.';

        if (separator === '.') {
            decimalCharacter = ',';
        }

        this.autoNumericOptions = {
            digitGroupSeparator: separator,
            decimalCharacter: decimalCharacter,
            modifyValueOnWheel: false,
            decimalPlaces: 0,
            selectOnFocus: false,
            formulaMode: true,
        };
    }

    afterRender() {
        super.afterRender();

        if (this.mode === this.MODE_EDIT) {
            this.$from = this.$el.find('[data-name="' + this.fromField + '"]');
            this.$to = this.$el.find('[data-name="' + this.toField + '"]');

            this.$from.on('change', () => {
                this.trigger('change');
            });

            this.$to.on('change', () => {
                this.trigger('change');
            });

            if (this.autoNumericOptions) {
                // noinspection JSUnusedGlobalSymbols
                this.autoNumericInstance1 = new AutoNumeric(this.$from.get(0), this.autoNumericOptions);
                // noinspection JSUnusedGlobalSymbols
                this.autoNumericInstance2 = new AutoNumeric(this.$to.get(0), this.autoNumericOptions);
            }
        }
    }

    validateRequired() {
        const validate = (name) => {
            if (this.model.isRequired(name)) {
                if (this.model.get(name) === null) {
                    var msg = this.translate('fieldIsRequired', 'messages')
                        .replace('{field}', this.getLabelText());

                    this.showValidationMessage(msg, '[data-name="' + name + '"]');

                    return true;
                }
            }
        };

        let result = false;

        result = validate(this.fromField) || result;
        result = validate(this.toField) || result;

        return result;
    }

    // noinspection JSUnusedGlobalSymbols
    validateInt() {
        const validate = (name) => {
            if (isNaN(this.model.get(name))) {
                var msg = this.translate('fieldShouldBeInt', 'messages')
                    .replace('{field}', this.getLabelText());

                this.showValidationMessage(msg, '[data-name="' + name + '"]');

                return true;
            }
        };

        let result = false;

        result = validate(this.fromField) || result;
        result = validate(this.toField) || result;

        return result;
    }

    // noinspection JSUnusedGlobalSymbols
    validateRange() {
        const validate = (name) => {
            var value = this.model.get(name);

            if (value === null) {
                return false;
            }

            var minValue = this.model.getFieldParam(name, 'min');
            var maxValue = this.model.getFieldParam(name, 'max');

            if (minValue !== null && maxValue !== null) {
                if (value < minValue || value > maxValue) {
                    let msg = this.translate('fieldShouldBeBetween', 'messages')
                        .replace('{field}', this.translate(name, 'fields', this.entityType))
                        .replace('{min}', minValue)
                        .replace('{max}', maxValue);

                    this.showValidationMessage(msg, '[data-name="' + name + '"]');

                    return true;
                }
            } else {
                if (minValue !== null) {
                    if (value < minValue) {
                        let msg = this.translate('fieldShouldBeLess', 'messages')
                            .replace('{field}', this.translate(name, 'fields', this.entityType))
                            .replace('{value}', minValue);

                        this.showValidationMessage(msg, '[data-name="' + name + '"]');

                        return true;
                    }
                } else if (maxValue !== null) {
                    if (value > maxValue) {
                        let msg = this.translate('fieldShouldBeGreater', 'messages')
                            .replace('{field}', this.translate(name, 'fields', this.entityType))
                            .replace('{value}', maxValue);

                        this.showValidationMessage(msg, '[data-name="' + name + '"]');

                        return true;
                    }
                }
            }
        };

        let result = false;

        result = validate(this.fromField) || result;
        result = validate(this.toField) || result;

        return result;
    }

    // noinspection JSUnusedGlobalSymbols
    validateOrder() {
        let fromValue = this.model.get(this.fromField);
        let toValue = this.model.get(this.toField);

        if (fromValue !== null && toValue !== null) {
            if (fromValue > toValue) {
                let msg = this.translate('fieldShouldBeGreater', 'messages')
                    .replace('{field}', this.translate(this.toField, 'fields', this.entityType))
                    .replace('{value}', this.translate(this.fromField, 'fields', this.entityType));

                this.showValidationMessage(msg, '[data-name="'+this.fromField+'"]');

                return true;
            }
        }
    }

    isRequired() {
        return this.model.getFieldParam(this.fromField, 'required') ||
            this.model.getFieldParam(this.toField, 'required');
    }

    parse(value) {
        return IntFieldView.prototype.parse.call(this, value);
    }

    formatNumber(value) {
        return IntFieldView.prototype.formatNumberDetail.call(this, value);
    }

    fetch() {
        let data = {};

        data[this.fromField] = this.parse(this.$from.val().trim());
        data[this.toField] = this.parse(this.$to.val().trim());

        return data;
    }
}

export default RangeIntFieldView;
PK]O�;P�P�views/fields/base.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/fields/base */

import View from 'view';
import Select from 'ui/select';
import $ from 'jquery';

/**
 * A base field view. Can be in different modes. Each mode uses a separate template.
 *
 * @todo Document events.
 */
class BaseFieldView extends View {

    /**
     * @typedef {Object} module:views/fields/base~options
     * @property {string} name A field name.
     * @property {module:model} [model] A model.
     * @property {module:views/fields/base~params | Object.<string, *>} [params] Parameters.
     * @property {boolean} [inlineEditDisabled] Disable inline edit.
     * @property {boolean} [readOnly] Read-only.
     * @property {string} [labelText] A custom label text.
     */

    /**
     * @typedef {Object} module:views/fields/base~params
     * @property {boolean} [inlineEditDisabled] Disable inline edit.
     * @property {boolean} [readOnly] Read-only.
     */

    /**
     * @param {module:views/fields/base~options | Object.<string, *>} options Options.
     */
    constructor(options) {
        super(options);

        this.name = options.name;
        this.labelText = options.labelText;
    }

    /**
     * A field type.
     *
     * @type {string}
     */
    type = 'base'

    /**
     * List mode template.
     *
     * @protected
     * @type {string}
     */
    listTemplate = 'fields/base/list'

    // noinspection JSUnusedGlobalSymbols
    /**
     * List-link mode template.
     *
     * @protected
     * @type {string}
     */
    listLinkTemplate = 'fields/base/list-link'

    /**
     * Detail mode template.
     *
     * @protected
     * @type {string}
     */
    detailTemplate = 'fields/base/detail'

    /**
     * Edit mode template.
     *
     * @protected
     * @type {string}
     */
    editTemplate = 'fields/base/edit'

    /**
     * Search mode template.
     *
     * @protected
     * @type {string}
     */
    searchTemplate = 'fields/base/search'

    // noinspection JSUnusedGlobalSymbols
    /**
     * @protected
     * @type {string}
     */
    listTemplateContent

    // noinspection JSUnusedGlobalSymbols
    /**
     * @protected
     * @type {string}
     */
    detailTemplateContent

    // noinspection JSUnusedGlobalSymbols
    /**
     * @protected
     * @type {string}
     */
    editTemplateContent

    /**
     * A validation list. There should be a `validate{Name}` method for each item.
     *
     * @type {string[]}
     */
    validations = ['required']

    /**
     * @const
     */
    MODE_LIST = 'list'

    /**
     * @const
     */
    MODE_LIST_LINK = 'listLink'

    /**
     * @const
     */
    MODE_DETAIL = 'detail'

    /**
     * @const
     */
    MODE_EDIT = 'edit'

    /**
     * @const
     */
    MODE_SEARCH = 'search'

    /**
     * A field name.
     *
     * @type {string}
     */
    name

    /**
     * Definitions.
     *
     * @type {Object}
     */
    defs = null

    /**
     * Field params.
     *
     * @type {Object.<string,*>}
     */
    params = null

    /**
     * A mode.
     *
     * @type {'list'|'listLink'|'detail'|'edit'|'search'}
     */
    mode = 'detail'

    /**
     * Search params.
     *
     * @type {Object.<string,*>|null}
     */
    searchParams = null

    /**
     * @private
     */
    _timeout = null

    /**
     * Inline edit disabled.
     *
     * @type {boolean}
     */
    inlineEditDisabled = false

    /**
     * Field is disabled.
     *
     * @type {boolean}
     */
    disabled = false

    /**
     * Field is read-only.
     *
     * @type {boolean}
     */
    readOnly = false

    /**
     * A label text.
     *
     * @type {string}
     * @protected
     */
    labelText

    /**
     * @type {string[]|null}
     */
    attributeList = null

    /**
     * Attribute values before edit.
     *
     * @type {Object.<string, *>|{}}
     */
    initialAttributes = null

    /**
     * @const
     */
    VALIDATION_POPOVER_TIMEOUT = 3000

    /**
     * @type {(function():boolean)}
     * @private
     * @internal
     */
    validateCallback

    /**
     * An element selector to point validation popovers to.
     *
     * @type {string}
     * @protected
     */
    validationElementSelector

    /**
     * A view-record helper.
     *
     * @type {module:view-record-helper}
     */
    recordHelper

    /**
     * @type {JQuery|null}
     * @private
     * @internal
     */
    $label = null

    /**
     * A form element.
     *
     * @type {JQuery|null}
     * @protected
     */
    $element = null

    /**
     * Is searchable once a search filter is added (no need to type or selecting anything).
     * Actual for search mode.
     *
     * @public
     * @type {boolean}
     */
    initialSearchIsNotIdle = false

    /**
     * An entity type.
     *
     * @private
     * @type {string|null}
     */
    entityType = null

    /**
     * A last validation message;
     *
     * @type {?string}
     */
    lastValidationMessage = null

    /**
     * Additional data.
     *
     * @type {Object.<string, *>}
     */
    dataObject

    /**
     * Is the field required.
     *
     * @returns {boolean}
     */
    isRequired() {
        return this.params.required;
    }

    /**
     * Get a cell element. Available only after the view is  rendered.
     *
     * @returns {JQuery}
     */
    get$cell() {
        return this.$el.parent();
    }

    /**
     * Get a cell element. Available only after the view is  rendered.
     *
     * @deprecated Use `get$cell`.
     * @returns {JQuery}
     */
    getCellElement() {
        return this.get$cell();
    }

    /**
     * Is in inline-edit mode.
     *
     * @return {boolean}
     */
    isInlineEditMode() {
        return !!this._isInlineEditMode;
    }

    /**
     * Set disabled.
     *
     * @param {boolean} [locked] Won't be able to set back.
     */
    setDisabled(locked) {
        this.disabled = true;

        if (locked) {
            this.disabledLocked = true;
        }
    }

    /**
     * Set not-disabled.
     */
    setNotDisabled() {
        if (this.disabledLocked) {
            return;
        }

        this.disabled = false;
    }

    /**
     * Set required.
     */
    setRequired() {
        this.params.required = true;

        if (this.isEditMode()) {
            if (this.isRendered()) {
                this.showRequiredSign();
            }
            else {
                this.once('after:render', () => {
                    this.showRequiredSign();
                });
            }
        }
    }

    /**
     * Set not required.
     */
    setNotRequired() {
        this.params.required = false;
        this.get$cell().removeClass('has-error');

        if (this.isEditMode()) {
            if (this.isRendered()) {
                this.hideRequiredSign();
            }
            else {
                this.once('after:render', () => {
                    this.hideRequiredSign();
                });
            }
        }
    }

    /**
     * Set read-only.
     *
     * @param {boolean} [locked] Won't be able to set back.
     * @return {Promise}
     */
    setReadOnly(locked) {
        if (this.readOnlyLocked) {
            return Promise.reject();
        }

        this.readOnly = true;

        if (locked) {
            this.readOnlyLocked = true;
        }

        if (this.isEditMode()) {
            if (this.isInlineEditMode()) {
                return this.inlineEditClose();
            }

            return this.setDetailMode()
                .then(() => this.reRender());
        }

        return Promise.resolve();
    }

    /**
     * Set not read only.
     */
    setNotReadOnly() {
        if (this.readOnlyLocked) {
            return;
        }

        this.readOnly = false;
    }

    /**
     * Get a label element. Available only after the view is rendered.
     *
     * @return {JQuery}
     */
    getLabelElement() {
        if (this.$label && this.$label.get(0) && !document.contains(this.$label.get(0))) {
            this.$label = undefined;
        }

        if (!this.$label || !this.$label.length) {
            this.$label = this.$el.parent().children('label');
        }

        return this.$label;
    }

    /**
     * Hide field and label. Available only after the view is rendered.
     */
    hide() {
        this.$el.addClass('hidden');
        let $cell = this.get$cell();

        $cell.children('label').addClass('hidden');
        $cell.addClass('hidden-cell');
    }

    /**
     * Show field and label. Available only after the view is rendered.
     */
    show() {
        this.$el.removeClass('hidden');

        let $cell = this.get$cell();

        $cell.children('label').removeClass('hidden');
        $cell.removeClass('hidden-cell');
    }

    /** @inheritDoc */
    data() {
        let data = {
            scope: this.model.entityType || this.model.name,
            name: this.name,
            defs: this.defs,
            params: this.params,
            value: this.getValueForDisplay(),
        };

        if (this.isSearchMode()) {
            data.searchParams = this.searchParams;
            data.searchData = this.searchData;
            data.searchValues = this.getSearchValues();
            data.searchType = this.getSearchType();
            data.searchTypeList = this.getSearchTypeList();
        }

        return data;
    }

    /**
     * Get a value for display. Is available by using a `{value}` placeholder in templates.
     *
     * @return {*}
     */
    getValueForDisplay() {
        return this.model.get(this.name);
    }

    /**
     * Is in list, detail or list-link mode.
     *
     * @returns {boolean}
     */
    isReadMode() {
        return this.mode === this.MODE_LIST ||
            this.mode === this.MODE_DETAIL ||
            this.mode === this.MODE_LIST_LINK;
    }

    /**
     * Is in list or list-link mode.
     *
     * @returns {boolean}
     */
    isListMode() {
        return this.mode === this.MODE_LIST || this.mode === this.MODE_LIST_LINK;
    }

    /**
     * Is in detail mode.
     *
     * @returns {boolean}
     */
    isDetailMode() {
        return this.mode === this.MODE_DETAIL;
    }

    /**
     * Is in edit mode.
     *
     * @returns {boolean}
     */
    isEditMode() {
        return this.mode === this.MODE_EDIT;
    }

    /**
     * Is in search mode.
     *
     * @returns {boolean}
     */
    isSearchMode() {
        return this.mode === this.MODE_SEARCH;
    }

    /**
     * Set detail mode.
     *
     * @returns {Promise}
     */
    setDetailMode() {
        return this.setMode(this.MODE_DETAIL) || Promise.resolve();
    }

    /**
     * Set edit mode.
     *
     * @returns {Promise}
     */
    setEditMode() {
        return this.setMode(this.MODE_EDIT) || Promise.resolve();
    }

    /**
     * Set a mode.
     *
     * @internal
     * @returns {Promise}
     */
    setMode(mode) {
        let modeIsChanged = this.mode !== mode && this.mode;
        let modeBefore = this.mode;

        this.mode = mode;

        let property = mode + 'Template';

        if (!(property in this)) {
            this[property] = 'fields/' + Espo.Utils.camelCaseToHyphen(this.type) + '/' + this.mode;
        }

        if (!this._hasTemplateContent) {
            this.setTemplate(this[property]);
        }

        let contentProperty = mode + 'TemplateContent';

        if (!this._hasTemplateContent) {
            if (contentProperty in this && this[contentProperty] != null) {
                this.setTemplateContent(this[contentProperty]);
            }
        }

        if (modeIsChanged) {
            if (modeBefore) {
                this.trigger('mode-changed');
            }

            return this._onModeSet();
        }

        return Promise.resolve();
    }

    /**
     * Called on mode change and on value change before re-rendering.
     * To be used for additional initialization that depends on field
     * values or mode.
     *
     * @protected
     * @returns {Promise|undefined}
     */
    prepare() {}

    /**
     * @private
     * @returns {Promise}
     */
    _onModeSet() {
        if (this.isListMode()) {
            return this.onListModeSet() || Promise.resolve();
        }

        if (this.isDetailMode()) {
            return this.onDetailModeSet() || Promise.resolve();
        }

        if (this.isEditMode()) {
            return this.onEditModeSet() || Promise.resolve();
        }

        return Promise.resolve();
    }

    /**
     * Additional initialization for the detail mode.
     *
     * @protected
     * @returns {Promise|undefined}
     */
    onDetailModeSet() {
        return this.prepare();
    }

    /**
     * Additional initialization for the edit mode.
     *
     * @protected
     * @returns {Promise|undefined}
     */
    onEditModeSet() {
        return this.prepare();
    }

    /**
     * Additional initialization for the list mode.
     *
     * @protected
     * @returns {Promise|undefined}
     */
    onListModeSet() {
        return this.prepare();
    }

    /** @inheritDoc */
    init() {
        this.validations = Espo.Utils.clone(this.validations);

        this._hasTemplateContent = !!this.templateContent;

        this.defs = this.options.defs || {};
        this.name = this.options.name || this.defs.name;
        this.params = this.options.params || this.defs.params || {};
        this.validateCallback = this.options.validateCallback;

        this.fieldType = this.model.getFieldParam(this.name, 'type') || this.type;
        this.entityType = this.model.entityType || this.model.name;

        this.recordHelper = this.options.recordHelper;
        this.dataObject = Espo.Utils.clone(this.options.dataObject || {});

        if (!this.labelText) {
            this.labelText = this.translate(this.name, 'fields', this.entityType);
        }

        this.getFieldManager().getParamList(this.type).forEach(d => {
            let name = d.name;

            if (!(name in this.params)) {
                this.params[name] = this.model.getFieldParam(this.name, name);

                if (typeof this.params[name] === 'undefined') {
                    this.params[name] = null;
                }
            }
        });

        let additionalParamList = ['inlineEditDisabled'];

        additionalParamList.forEach((item) => {
            this.params[item] = this.model.getFieldParam(this.name, item) || null;
        });

        this.readOnly = this.readOnly || this.params.readOnly ||
            this.model.getFieldParam(this.name, 'readOnly') ||
            this.model.getFieldParam(this.name, 'clientReadOnly');

        this.readOnlyLocked = this.options.readOnlyLocked || this.readOnly;

        this.inlineEditDisabled = this.options.inlineEditDisabled ||
            this.params.inlineEditDisabled || this.inlineEditDisabled;

        this.readOnly = this.readOnlyLocked || this.options.readOnly || false;

        this.tooltip = this.options.tooltip || this.params.tooltip ||
            this.model.getFieldParam(this.name, 'tooltip') || this.tooltip;

        if (this.options.readOnlyDisabled) {
            this.readOnly = false;
        }

        this.disabledLocked = this.options.disabledLocked || false;
        this.disabled = this.disabledLocked || this.options.disabled || this.disabled;

        let mode = this.options.mode || this.mode || this.MODE_DETAIL;

        if (mode === this.MODE_EDIT && this.readOnly) {
            mode = this.MODE_DETAIL;
        }

        this.mode = undefined;

        this.wait(
            this.setMode(mode)
        );

        if (this.isSearchMode()) {
            this.searchParams = _.clone(this.options.searchParams || {});
            this.searchData = {};
            this.setupSearch();

            this.events['keydown.' + this.cid] = /** JQueryKeyEventObject */e => {
                if (Espo.Utils.getKeyFromKeyEvent(e) === 'Control+Enter') {
                    this.trigger('search');
                }
            };
        }

        this.on('highlight', () => {
            let $cell = this.get$cell();

            $cell.addClass('highlighted');
            $cell.addClass('transition');

            setTimeout(() => {
                $cell.removeClass('highlighted');
            }, 3000);

            setTimeout(() => {
                $cell.removeClass('transition');
            }, 3000 + 2000);
        });

        this.on('invalid', () => {
            let $cell = this.get$cell();

            $cell.addClass('has-error');

            this.$el.one('click', () => {
                $cell.removeClass('has-error');
            });

            this.once('render', () => {
                $cell.removeClass('has-error');
            });
        });

        this.on('after:render', () => {
            if (this.isEditMode()) {
                if (this.hasRequiredMarker()) {
                    this.showRequiredSign();

                    return;
                }

                this.hideRequiredSign();

                return;
            }

            if (this.hasRequiredMarker()) {
                this.hideRequiredSign();
            }

            if (this.isSearchMode()) {
                let $searchType = this.$el.find('select.search-type');

                if ($searchType.length) {
                    Select.init($searchType, {matchAnyWord: true});
                }
            }
        });

        if ((this.isDetailMode() || this.isEditMode()) && this.tooltip) {
            this.initTooltip();
        }

        if (this.isDetailMode()) {
            if (!this.inlineEditDisabled) {
                this.listenToOnce(this, 'after:render', () => this.initInlineEdit());
            }
        }

        if (!this.isSearchMode()) {
            this.attributeList = this.getAttributeList(); // for backward compatibility, to be removed

            this.listenTo(this.model, 'change', (model, options) => {
                if (options.ui) {
                    return;
                }

                let changed = false;

                for (let attribute of this.getAttributeList()) {
                    if (model.hasChanged(attribute)) {
                        changed = true;

                        break;
                    }
                }

                if (!changed) {
                    return;
                }

                if (options.skipReRenderInEditMode && this.isEditMode()) {
                    return;
                }

                if (options.skipReRender) {
                    return;
                }

                let reRender = () => {
                    if (!this.isRendered() && !this.isBeingRendered()) {
                        return;
                    }

                    this.reRender();

                    if (options.highlight) {
                        this.trigger('highlight');
                    }
                };

                if (!this.isReady) {
                    this.once('ready', () => {
                        const promise = this.prepare();

                        if (promise) {
                            promise.then(() => reRender());
                        }
                    });

                    return;
                }

                let promise = this.prepare();

                if (promise) {
                    promise.then(() => reRender());

                    return;
                }

                reRender();
            });

            this.listenTo(this, 'change', () => {
                let attributes = this.fetch();

                this.model.set(attributes, {ui: true});
            });
        }
    }

    /** @inheritDoc */
    setupFinal() {
        this.wait(
            this._onModeSet()
        );
    }

    /**
     * @internal
     * @private
     */
    initTooltip() {
        let $a;

        this.once('after:render', () => {
            $a = $('<a>')
                .attr('role', 'button')
                .attr('tabindex', '-1')
                .addClass('text-muted field-info')
                .append(
                    $('<span>').addClass('fas fa-info-circle')
                );

            let $label = this.getLabelElement();

            $label.append(' ');

            this.getLabelElement().append($a);

            let tooltipText = this.options.tooltipText || this.tooltipText;

            if (!tooltipText && typeof this.tooltip === 'string') {
                let [scope, field] = this.tooltip.includes('.') ?
                    this.tooltip.split('.') :
                    [this.entityType, this.tooltip];

                tooltipText = this.translate(field, 'tooltips', scope);
            }

            tooltipText = tooltipText || this.translate(this.name, 'tooltips', this.entityType) || '';
            tooltipText = this.getHelper()
                .transformMarkdownText(tooltipText, {linksInNewTab: true}).toString();

            Espo.Ui.popover($a, {
                placement: 'bottom',
                content: tooltipText,
                preventDestroyOnRender: true,
            }, this);
        });
    }

    /**
     * Show a required-field sign.
     *
     * @private
     */
    showRequiredSign() {
        let $label = this.getLabelElement();
        let $sign = $label.find('span.required-sign');

        if ($label.length && !$sign.length) {
            let $text = $label.find('span.label-text');

            $('<span class="required-sign"> *</span>').insertAfter($text);
            $sign = $label.find('span.required-sign');
        }

        $sign.show();
    }

    /**
     * Hide a required-field sign.
     *
     * @private
     */
    hideRequiredSign() {
        let $label = this.getLabelElement();
        let $sign = $label.find('span.required-sign');

        $sign.hide();
    }

    /**
     * Get search-params data.
     *
     * @protected
     * @return {Object.<string,*>}
     */
    getSearchParamsData() {
        return this.searchParams.data || {};
    }

    /**
     * Get search values.
     *
     * @protected
     * @return {Object.<string,*>}
     */
    getSearchValues() {
        return this.getSearchParamsData().values || {};
    }

    /**
     * Get a current search type.
     *
     * @protected
     * @return {string}
     */
    getSearchType() {
        return this.getSearchParamsData().type || this.searchParams.type;
    }

    /**
     * Get the search type list.
     *
     * @protected
     * @returns {string[]}
     */
    getSearchTypeList() {
        return this.searchTypeList;
    }

    /**
     * @private
     * @internal
     */
    initInlineEdit() {
        let $cell = this.get$cell();

        let $editLink = $('<a>')
            .attr('role', 'button')
            .addClass('pull-right inline-edit-link hidden')
            .append(
                $('<span>').addClass('fas fa-pencil-alt fa-sm')
            );

        if ($cell.length === 0) {
            this.listenToOnce(this, 'after:render', () => this.initInlineEdit());

            return;
        }

        $cell.prepend($editLink);

        $editLink.on('click', () => this.inlineEdit());

        $cell
            .on('mouseenter', (e) => {
                e.stopPropagation();

                if (this.disabled || this.readOnly) {
                    return;
                }

                if (this.isDetailMode()) {
                    $editLink.removeClass('hidden');
                }
            })
            .on('mouseleave', (e) => {
                e.stopPropagation();

                if (this.isDetailMode()) {
                    $editLink.addClass('hidden');
                }
            });

        this.on('after:render', () => {
            if (!this.isDetailMode()) {
                $editLink.addClass('hidden');
            }
        });
    }

    /**
     * Initializes a form element reference.
     *
     * @protected
     */
    initElement() {
        this.$element = this.$el.find('[data-name="' + this.name + '"]');

        if (!this.$element.length) {
            this.$element = this.$el.find('[name="' + this.name + '"]');
        }

        if (!this.$element.length) {
            this.$element = this.$el.find('.main-element');
        }

        if (this.isEditMode()) {
            this.$element.on('change', () => {
                this.trigger('change');
            });
        }
    }

    /** @inheritDoc */
    afterRender() {
        if (this.isEditMode() || this.isSearchMode()) {
            this.initElement();
        }

        if (this.isReadMode()) {
            this.afterRenderRead();
        }

        if (this.isListMode()) {
            this.afterRenderList();
        }

        if (this.isDetailMode()) {
            this.afterRenderDetail();
        }

        if (this.isEditMode()) {
            this.afterRenderEdit();
        }

        if (this.isSearchMode()) {
            this.afterRenderSearch();
        }
    }

    /**
     * Called after the view is rendered in list or read mode.
     *
     * @protected
     */
    afterRenderRead() {}

    /**
     * Called after the view is rendered in list mode.
     *
     * @protected
     */
    afterRenderList() {}

    /**
     * Called after the view is rendered in detail mode.
     *
     * @protected
     */
    afterRenderDetail() {}

    /**
     * Called after the view is rendered in edit mode.
     *
     * @protected
     */
    afterRenderEdit() {}

    /**
     * Called after the view is rendered in search mode.
     *
     * @protected
     */
    afterRenderSearch() {}

    /**
     * Initialization.
     */
    setup() {}

    /**
     * Initialization for search mode.
     *
     * @protected
     */
    setupSearch() {}

    /**
     * Get list of model attributes that relate to the field.
     * Changing of any attributes makes the field to re-render.
     *
     * @return {string[]}
     */
    getAttributeList() {
        return this.getFieldManager().getAttributeList(this.fieldType, this.name);
    }

    /**
     * Invoke inline-edit saving.
     *
     * @param {{[bypassClose]: boolean}} [options]
     */
    inlineEditSave(options) {
        options = options || {}

        if (this.recordHelper) {
            this.recordHelper.trigger('inline-edit-save', this.name, options);

            return;
        }

        // Code below supposed not to be executed.

        let data = this.fetch();

        let model = this.model;
        let prev = this.initialAttributes;

        model.set(data, {silent: true});
        data = model.attributes;

        let attrs = false;

        for (let attr in data) {
            if (_.isEqual(prev[attr], data[attr])) {
                continue;
            }

            (attrs || (attrs = {}))[attr] = data[attr];
        }

        if (!attrs) {
            this.inlineEditClose();
        }

        let isInvalid = this.validateCallback ? this.validateCallback() : this.validate();

        if (isInvalid) {
            Espo.Ui.error(this.translate('Not valid'));

            model.set(prev, {silent: true});

            return;
        }

        Espo.Ui.notify(this.translate('saving', 'messages'));

        model
            .save(/** @type Object */attrs, {patch: true})
            .then(() => {
                this.trigger('after:inline-save');
                this.trigger('after:save');

                model.trigger('after:save');

                Espo.Ui.success(this.translate('Saved'));
            })
            .catch(() => {
                Espo.Ui.error(this.translate('Error occurred'));

                model.set(prev, {silent: true});

                this.reRender();
            });

        if (!options.bypassClose) {
            this.inlineEditClose(true);
        }
    }

    /**
     * @public
     */
    removeInlineEditLinks() {
        let $cell = this.get$cell();

        $cell.find('.inline-save-link').remove();
        $cell.find('.inline-cancel-link').remove();
        $cell.find('.inline-edit-link').addClass('hidden');
    }

    /**
     * @private
     */
    addInlineEditLinks() {
        let $cell = this.get$cell();

        let $saveLink = $('<a>')
            .attr('role', 'button')
            .attr('tabindex', '-1')
            .addClass('pull-right inline-save-link')
            .attr('title', 'Ctrl+Enter')
            .text(this.translate('Update'));

        let $cancelLink = $('<a>')
            .attr('role', 'button')
            .attr('tabindex', '-1')
            .addClass('pull-right inline-cancel-link')
            .attr('title', 'Esc')
            .text(this.translate('Cancel'));

        $cell.prepend($saveLink);
        $cell.prepend($cancelLink);

        $cell.find('.inline-edit-link').addClass('hidden');

        $saveLink.click(() => {
            this.inlineEditSave();
        });

        $cancelLink.click(() => {
            this.inlineEditClose();
        });
    }

    /**
     * @public
     * @param {boolean} value
     * @internal
     */
    setIsInlineEditMode(value) {
        this._isInlineEditMode = value;
    }

    /**
     * Exist inline-edit mode.
     *
     * @param {boolean} [noReset]
     * @return {Promise}
     */
    inlineEditClose(noReset) {
        this.trigger('inline-edit-off', {noReset: noReset});

        this.$el.off('keydown.inline-edit');

        this._isInlineEditMode = false;

        if (!this.isEditMode()) {
            return Promise.resolve();
        }

        if (!noReset) {
            this.model.set(this.initialAttributes, {skipReRenderInEditMode: true});
        }

        let promise = this.setDetailMode()
            .then(() => this.reRender(true))
            .then(() => this.removeInlineEditLinks());

        this.trigger('after:inline-edit-off', {noReset: noReset});

        return promise;
    }

    /**
     * Switch to inline-edit mode.
     *
     * @return {Promise}
     */
    inlineEdit() {
        this.trigger('edit', this);

        this.initialAttributes = this.model.getClonedAttributes();

        this._isInlineEditMode = true;

        let promise = this.setEditMode()
            .then(() => this.reRender(true))
            .then(() => this.addInlineEditLinks())
            .then(() => {
                this.$el.on('keydown.inline-edit', e => {
                    let key = Espo.Utils.getKeyFromKeyEvent(e);

                    if (key === 'Control+Enter') {
                        e.stopPropagation();

                        this.inlineEditSave();

                        setTimeout(() => {
                            this.get$cell().focus();
                        }, 100);

                        return;
                    }

                    if (key === 'Escape') {
                        e.stopPropagation();

                        this.inlineEditClose()
                            .then(() => {
                                this.get$cell().focus();
                            });

                        return;
                    }

                    if (key === 'Control+KeyS') {
                        e.preventDefault();
                        e.stopPropagation();

                        this.inlineEditSave({bypassClose: true});
                    }
                });

                setTimeout(() => this.focusOnInlineEdit(), 10);
            });

        this.trigger('inline-edit-on');

        return promise;
    }

    /**
     * @protected
     */
    focusOnInlineEdit() {
        let $element = this.$element && this.$element.length ?
            this.$element :
            this.$el.find('.form-control').first();

        if (!$element) {
            return;
        }

        $element.first().focus();
    }

    /**
     * Suspend a validation message.
     *
     * @internal
     * @param {number} [time=200]
     */
    suspendValidationMessage(time) {
        this.validationMessageSuspended = true;

        setTimeout(() => this.validationMessageSuspended = false, time || 200);
    }

    /**
     * Show a validation message.
     *
     * @param {string} message A message.
     * @param {string|JQuery|Element} [target] A target element or selector.
     * @param {module:view} [view] A child view that contains the target. The closest view should to passed.
     *   Should be omitted if there is no child views or the target is not rendered by a child view.
     */
    showValidationMessage(message, target, view) {
        if (this.validationMessageSuspended) {
            return;
        }

        let $el;

        target = target || this.validationElementSelector || '.main-element';

        if (typeof target === 'string' || target instanceof String) {
            $el = this.$el.find(target);
        } else {
            $el = $(target);
        }

        if (!$el.length && this.$element) {
            $el = this.$element;
        }

        if (!$el.length) {
            $el = this.$el;
        }

        if ($el.length) {
            const rect = $el.get(0).getBoundingClientRect();

            this.lastValidationMessage = message;

            if (rect.top === 0 && rect.bottom === 0 && rect.left === 0) {
                return;
            }
        }

        this._popoverMap = this._popoverMap || new WeakMap();
        const element = $el.get(0);

        if (!element) {
            return;
        }

        if (this._popoverMap[element]) {
            try {
                this._popoverMap[element].detach();
            }
            catch (e) {}
        }

        const popover = Espo.Ui.popover($el, {
            placement: 'bottom',
            container: 'body',
            content: this.getHelper().transformMarkdownText(message).toString(),
            trigger: 'manual',
            noToggleInit: true,
            noHideOnOutsideClick: true,
        }, view || this);

        popover.show();

        this._popoverMap[element] = popover;

        $el.closest('.field').one('mousedown click', () => popover.destroy());

        this.once('render remove', () => popover.destroy());

        if (this._timeout) {
            clearTimeout(this._timeout);
        }

        this._timeout = setTimeout(() => popover.destroy(), this.VALIDATION_POPOVER_TIMEOUT);
    }

    /**
     * Validate field values.
     *
     * @return {boolean} True if not valid.
     */
    validate() {
        this.lastValidationMessage = null;

        for (let i in this.validations) {
            let method = 'validate' + Espo.Utils.upperCaseFirst(this.validations[i]);

            if (this[method].call(this)) {
                this.trigger('invalid');

                return true;
            }
        }

        return false;
    }

    /**
     * Get a label text.
     *
     * @returns {string}
     */
    getLabelText() {
        return this.labelText;
    }

    /**
     * Validate required.
     *
     * @return {boolean}
     */
    validateRequired() {
        if (this.isRequired()) {
            if (this.model.get(this.name) === '' || this.model.get(this.name) === null) {
                let msg = this.translate('fieldIsRequired', 'messages')
                    .replace('{field}', this.getLabelText());

                this.showValidationMessage(msg);

                return true;
            }
        }
    }

    /**
     * Defines whether the field should have a required-marker rendered.
     *
     * @protected
     * @return {boolean}
     */
    hasRequiredMarker() {
        return this.isRequired();
    }

    /**
     * Fetch field values to the model.
     */
    fetchToModel() {
        this.model.set(this.fetch(), {silent: true});
    }

    /**
     * Fetch field values from DOM.
     *
     * @return {Object.<string, *>}
     */
    fetch() {
        if (!this.$element.length) {
            return {};
        }

        let data = {};

        data[this.name] = this.$element.val().trim();

        return data;
    }

    /**
     * Fetch search data from DOM.
     *
     * @return {Object.<string, *>|null}
     */
    fetchSearch() {
        let value = this.$element.val().toString().trim();

        if (value) {
            return {
                type: 'equals',
                value: value,
            };
        }

        return null;
    }

    /**
     * Fetch a search type from DOM.
     *
     * @return {string}
     */
    fetchSearchType() {
        return this.$el.find('select.search-type').val();
    }
}

export default BaseFieldView;
PK]���!views/fields/datetime-optional.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/fields/datetime-optional */

import DatetimeFieldView from 'views/fields/datetime';
import moment from 'moment';

/**
 * A date-time or date.
 */
class DatetimeOptionalFieldView extends DatetimeFieldView {

    type = 'datetimeOptional'

    setup() {
        super.setup();

        this.noneOption = this.translate('None');
        this.nameDate = this.name + 'Date';
    }

    isDate() {
        let dateValue = this.model.get(this.nameDate);

        if (dateValue && dateValue !== '') {
            return true;
        }

        return false;
    }

    data() {
        let data = super.data();

        if (this.isDate()) {
            let dateValue = this.model.get(this.nameDate);

            data.date = this.getDateTime().toDisplayDate(dateValue);
            data.time = this.noneOption;
        }

        return data;
    }

    getDateStringValue() {
        if (this.isDate()) {
            var dateValue = this.model.get(this.nameDate);

            return this.stringifyDateValue(dateValue);
        }

        return super.getDateStringValue();
    }

    setDefaultTime() {
        this.$time.val(this.noneOption);
    }

    initTimepicker() {
        let $time = this.$time;

        let o = {
            step: this.params.minuteStep || 30,
            scrollDefaultNow: true,
            timeFormat: this.timeFormatMap[this.getDateTime().timeFormat],
            noneOption: [{
                label: this.noneOption,
                value: this.noneOption,
            }],
        };

        if (this.emptyTimeInInlineEditDisabled && this.isInlineEditMode() || this.noneOptionIsHidden) {
            delete o.noneOption;
        }

        $time.timepicker(o);

        $time.parent().find('button.time-picker-btn').on('click', () => {
            $time.timepicker('show');
        });
    }

    fetch() {
        let data = {};

        let date = this.$date.val();
        let time = this.$time.val();
        let value = null;

        if (time !== this.noneOption && time !== '') {
            if (date !== '' && time !== '') {
                value = this.parse(date + ' ' + time);
            }

            data[this.name] = value;
            data[this.nameDate] = null;

            return data;
        }

        if (date !== '') {
            data[this.nameDate] = this.getDateTime().fromDisplayDate(date);

            let dateTimeValue = data[this.nameDate] + ' 00:00:00';

            dateTimeValue = moment
                .tz(dateTimeValue, this.getConfig().get('timeZone') || 'UTC')
                .add(this.isEnd ? 1 : 0, 'days')
                .utc()
                .format(this.getDateTime().internalDateTimeFullFormat);

            data[this.name] = dateTimeValue;

            return data;
        }

        data[this.nameDate] = null;
        data[this.name] = null;

        return data;
    }

    validateAfter() {
        let field = this.model.getFieldParam(this.name, 'after');

        if (!field) {
            return;
        }

        let fieldDate = field + 'Date';
        let value = this.model.get(this.name) || this.model.get(this.nameDate);
        let otherValue = this.model.get(field) || this.model.get(fieldDate);

        if (!(value && otherValue)) {
            return;
        }

        let isNotValid = this.validateAfterAllowSameDay && this.model.get(this.nameDate) ?
            moment(value).unix() < moment(otherValue).unix() :
            moment(value).unix() <= moment(otherValue).unix();

        if (isNotValid) {
            let msg = this.translate('fieldShouldAfter', 'messages')
                .replace('{field}', this.getLabelText())
                .replace('{otherField}', this.translate(field, 'fields', this.entityType));

            this.showValidationMessage(msg);

            return true;
        }
    }

    validateBefore() {
        var field = this.model.getFieldParam(this.name, 'before');

        if (!field) {
            return;
        }

        let fieldDate = field + 'Date';
        let value = this.model.get(this.name) || this.model.get(this.nameDate);
        let otherValue = this.model.get(field) || this.model.get(fieldDate);

        if (!(value && otherValue)) {
            return;
        }

        if (moment(value).unix() >= moment(otherValue).unix()) {
            let msg = this.translate('fieldShouldBefore', 'messages')
                .replace('{field}', this.getLabelText())
                .replace('{otherField}', this.translate(field, 'fields', this.entityType));

            this.showValidationMessage(msg);

            return true;
        }
    }

    validateRequired() {
        if (!this.isRequired()) {
            return;
        }

        if (this.model.get(this.name) === null && this.model.get(this.nameDate) === null) {
            let msg = this.translate('fieldIsRequired', 'messages')
                .replace('{field}', this.getLabelText());

            this.showValidationMessage(msg);

            return true;
        }
    }
}

// noinspection JSUnusedGlobalSymbols
export default DatetimeOptionalFieldView;
PK]�}���views/fields/address-state.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import VarcharFieldView from 'views/fields/varchar';

class AddressStateFieldView extends VarcharFieldView {

    setupOptions() {
        let stateList = this.getConfig().get('addressStateList') || [];

        if (stateList.length) {
            this.params.options = Espo.Utils.clone(stateList);
        }
    }
}

export default AddressStateFieldView;
PK]����views/fields/map.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 ************************************************************************/

import BaseFieldView from 'views/fields/base';

class MapFieldView extends BaseFieldView {

    type = 'map'

    detailTemplate = 'fields/map/detail'
    listTemplate = 'fields/map/detail'

    addressField = null
    provider = null
    height = 300

    // noinspection JSCheckFunctionSignatures
    data() {
        const data = super.data();

        data.hasAddress = this.hasAddress();

        // noinspection JSValidateTypes
        return data;
    }

    setup() {
        this.addressField = this.name.slice(0, this.name.length - 3);

        this.provider = this.options.provider || this.params.provider;
        this.height = this.options.height || this.params.height || this.height;

        const addressAttributeList = Object.keys(this.getMetadata().get('fields.address.fields') || {})
            .map(a => this.addressField + Espo.Utils.upperCaseFirst(a));

        this.listenTo(this.model, 'sync', model => {
            let isChanged = false;

            addressAttributeList.forEach(attribute => {
                if (model.hasChanged(attribute)) {
                    isChanged = true;
                }
            });

            if (isChanged && this.isRendered()) {
                this.reRender();
            }
        });

        this.listenTo(this.model, 'after:save', () => {
            if (this.isRendered()) {
                this.reRender();
            }
        });
    }

    hasAddress() {
        return !!this.model.get(this.addressField + 'City') ||
            !!this.model.get(this.addressField + 'PostalCode');
    }

    onRemove() {
        $(window).off('resize.' + this.cid);
    }

    afterRender() {
        this.addressData = {
            city: this.model.get(this.addressField + 'City'),
            street: this.model.get(this.addressField + 'Street'),
            postalCode: this.model.get(this.addressField + 'PostalCode'),
            country: this.model.get(this.addressField + 'Country'),
            state: this.model.get(this.addressField + 'State'),
        };

        this.$map = this.$el.find('.map');

        if (this.hasAddress()) {
            this.processSetHeight(true);

            if (this.height === 'auto') {
                $(window).off('resize.' + this.cid);
                $(window).on('resize.' + this.cid, this.processSetHeight.bind(this));
            }

            let methodName = 'afterRender' + this.provider.replace(/\s+/g, '');

            if (typeof this[methodName] === 'function') {
                this[methodName]();
            }
            else {
                let implClassName = this.getMetadata()
                    .get(['clientDefs', 'AddressMap', 'implementations', this.provider]);

                if (implClassName) {
                    Espo.loader.require(implClassName, impl => {
                        impl.render(this);
                    });
                }
            }
        }
    }

    // noinspection JSUnusedGlobalSymbols
    afterRenderGoogle() {
        if (window.google && window.google.maps) {
            this.initMapGoogle();

            return;
        }

        // noinspection SpellCheckingInspection
        if (typeof window.mapapiloaded === 'function') {
            // noinspection SpellCheckingInspection
            let mapapiloaded = window.mapapiloaded;

            // noinspection SpellCheckingInspection
            window.mapapiloaded = () => {
                this.initMapGoogle();
                mapapiloaded();
            };

            return;
        }

        // noinspection SpellCheckingInspection
        window.mapapiloaded = () => {
            this.initMapGoogle();
        };

        let src = 'https://maps.googleapis.com/maps/api/js?callback=mapapiloaded';
        let apiKey = this.getConfig().get('googleMapsApiKey');

        if (apiKey) {
            src += '&key=' + apiKey;
        }

        let scriptElement = document.createElement('script');

        scriptElement.setAttribute('async', 'async');
        scriptElement.src = src;

        document.head.appendChild(scriptElement);
    }

    processSetHeight(init) {
        let height = this.height;

        if (this.height === 'auto') {
            height = this.$el.parent().height();

            if (init && height <= 0) {
                setTimeout(() => {
                    this.processSetHeight(true);
                }, 50);

                return;
            }
        }

        this.$map.css('height', height + 'px');
    }

    initMapGoogle() {
        const geocoder = new google.maps.Geocoder();
        let map;

        try {
            // noinspection SpellCheckingInspection
            map = new google.maps.Map(this.$el.find('.map').get(0), {
                zoom: 15,
                center: {lat: 0, lng: 0},
                scrollwheel: false,
            });
        }
        catch (e) {
            console.error(e.message);

            return;
        }

        let address = '';

        if (this.addressData.street) {
            address += this.addressData.street;
        }

        if (this.addressData.city) {
            if (address !== '') {
                address += ', ';
            }

            address += this.addressData.city;
        }

        if (this.addressData.state) {
            if (address !== '') {
                address += ', ';
            }

            address += this.addressData.state;
        }

        if (this.addressData.postalCode) {
            if (this.addressData.state || this.addressData.city) {
                address += ' ';
            }
            else {
                if (address) {
                    address += ', ';
                }
            }

            address += this.addressData.postalCode;
        }

        if (this.addressData.country) {
            if (address !== '') {
                address += ', ';
            }

            address += this.addressData.country;
        }

        geocoder.geocode({'address': address}, (results, status) => {
            if (status === google.maps.GeocoderStatus.OK) {
                map.setCenter(results[0].geometry.location);

                new google.maps.Marker({
                    map: map,
                    position: results[0].geometry.location,
                });
            }
        });
    }
}

export default MapFieldView;
PK]F��A��views/fields/barcode.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import VarcharFieldView from 'views/fields/varchar';

let JsBarcode;
let QRCode;

class BarcodeFieldView extends VarcharFieldView {

    type = 'barcode'

    listTemplate = 'fields/barcode/detail'
    detailTemplate = 'fields/barcode/detail'

    setup() {
        let maxLength = 255;

        // noinspection SpellCheckingInspection
        switch (this.params.codeType) {
            case 'EAN2':
                maxLength = 2; break;
            case 'EAN5':
                maxLength = 5; break;
            case 'EAN8':
                maxLength = 8; break;
            case 'EAN13':
                maxLength = 13; break;
            case 'UPC':
                maxLength = 12; break;
            case 'UPCE':
                maxLength = 11; break;
            case 'ITF14':
                maxLength = 14; break;
            case 'pharmacode':
                maxLength = 6; break;
        }

        this.params.maxLength = maxLength;

        // noinspection SpellCheckingInspection
        if (this.params.codeType !== 'QRcode') {
            this.isSvg = true;

            this.wait(
                Espo.loader.requirePromise('lib!jsbarcode')
                    .then(lib => JsBarcode = lib)
            );
        }
        else {
            this.wait(
                Espo.loader.requirePromise('lib!qrcodejs')
                    .then(lib => QRCode = lib)
            );
        }

        super.setup();

        $(window).on('resize.' + this.cid, () => {
            if (!this.isRendered()) {
                return;
            }

            this.controlWidth();
        });

        this.listenTo(this.recordHelper, 'panel-show', () => this.controlWidth());
    }

    data() {
        let data = super.data();

        data.isSvg = this.isSvg;

        return data;
    }

    onRemove() {
        $(window).off('resize.' + this.cid);
    }

    afterRender() {
        super.afterRender();

        if (this.isListMode() || this.isDetailMode) {
            let value = this.model.get(this.name);

            if (value) {
                // noinspection SpellCheckingInspection
                if (this.params.codeType === 'QRcode') {
                    this.initQrcode(value);
                }
                else {
                    let $barcode = $(this.getSelector() + ' .barcode');

                    if ($barcode.length) {
                        this.initBarcode(value);
                    }
                    else {
                        // SVG may be not available yet (in webkit).
                        setTimeout(() => {
                            this.initBarcode(value);
                            this.controlWidth();
                        }, 100);
                    }

                }
            }

            this.controlWidth();
        }
    }

    initQrcode(value) {
        let size = 128;

        if (value.length > 192) {
            size *= 2;
        }

        if (this.isListMode()) {
            size = 64;
        }

        let containerWidth = this.$el.width() ;

        if (containerWidth < size && containerWidth) {
            size = containerWidth;
        }

        let $barcode = this.$el.find('.barcode');

        let init = (level) => {
            let options = {
                text: value,
                width: size,
                height: size,
                colorDark : '#000000',
                colorLight : '#ffffff',
                correctLevel : level || QRCode.CorrectLevel.H,
            };

            new QRCode($barcode.get(0), options);
        };

        try {
            init();
        }
        catch (e) {
            try {
                $barcode.empty();

                init(QRCode.CorrectLevel.L);
            }
            catch (e) {
                console.error(this.name + ': ' + e.message);
            }
        }
    }

    initBarcode(value) {
        JsBarcode(this.getSelector() + ' .barcode', value, {
            format: this.params.codeType,
            height: 50,
            fontSize: 14,
            margin: 0,
            lastChar: this.params.lastChar,
        });
    }

    controlWidth() {
        this.$el.find('.barcode').css('max-width', this.$el.width() + 'px');
    }
}

export default BarcodeFieldView;
PK]g��nIIviews/fields/foreign-bool.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import BoolFieldView from 'views/fields/bool';
import Helper from 'helpers/misc/foreign-field';

class ForeignBoolFieldView extends BoolFieldView {

    type = 'foreign'

    setup() {
        super.setup();

        let helper = new Helper(this);

        let foreignParams = helper.getForeignParams();

        for (let param in foreignParams) {
            this.params[param] = foreignParams[param];
        }
    }
}

export default ForeignBoolFieldView;
PK]@%���views/fields/number.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import VarcharFieldView from 'views/fields/varchar';

class NumberFieldView extends VarcharFieldView {

    type = 'number'

    validations = []

    inlineEditDisabled = true
    readOnly = true

    /** @inheritDoc */
    fetch() {
        return {};
    }
}

export default NumberFieldView;
PK]�-�'�[�[views/fields/array.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/fields/array */

import BaseFieldView from 'views/fields/base';
import RegExpPattern from 'helpers/reg-exp-pattern';
import MultiSelect from 'ui/multi-select';

/**
 * An array field.
 */
class ArrayFieldView extends BaseFieldView {

    type = 'array'

    listTemplate = 'fields/array/list'
    listLinkTemplate = 'fields/array/list-link'
    detailTemplate = 'fields/array/detail'
    editTemplate = 'fields/array/edit'
    searchTemplate = 'fields/array/search'

    searchTypeList = ['anyOf', 'noneOf', 'allOf', 'isEmpty', 'isNotEmpty']
    maxItemLength = null
    validations = ['required', 'maxCount']
    MAX_ITEM_LENGTH = 100

    /**
     * An add-item model view.
     *
     * @protected
     * @type {string}
     */
    addItemModalView = 'views/modals/array-field-add'
    /**
     * @protected
     * @type {string}
     */
    itemDelimiter = ':,:'
    /**
     * @protected
     * @type {boolean}
     */
    matchAnyWord = true
    /**
     * @protected
     * @type {Object|null}
     */
    translatedOptions = null

    /** @inheritDoc */
    data() {
        let itemHtmlList = [];

        (this.selected || []).forEach(value => {
            itemHtmlList.push(this.getItemHtml(value));
        });

        return {
            ...super.data(),
            selected: this.selected,
            translatedOptions: this.translatedOptions,
            hasOptions: !!this.params.options,
            itemHtmlList: itemHtmlList,
            isEmpty: (this.selected || []).length === 0,
            valueIsSet: this.model.has(this.name),
            maxItemLength: this.maxItemLength || this.MAX_ITEM_LENGTH,
            allowCustomOptions: this.allowCustomOptions,
        };
    }

    /** @inheritDoc */
    events = {
        /** @this ArrayFieldView */
        'click [data-action="removeValue"]': function (e) {
            let value = $(e.currentTarget).attr('data-value').toString();

            this.removeValue(value);
            this.focusOnElement();
        },
        /** @this ArrayFieldView */
        'click [data-action="showAddModal"]': function () {
            this.actionAddItem();
        },
    }

    setup() {
        super.setup();

        this.noEmptyString = this.params.noEmptyString;

        this.listenTo(this.model, 'change:' + this.name, () => {
            this.selected = Espo.Utils.clone(this.model.get(this.name)) || [];
        });

        this.selected = Espo.Utils.clone(this.model.get(this.name) || []);

        if (Object.prototype.toString.call(this.selected) !== '[object Array]')    {
            this.selected = [];
        }

        let optionsPath = this.params.optionsPath;
        /** @type {?string} */
        let optionsReference = this.params.optionsReference;

        if (!optionsPath && optionsReference) {
            let [refEntityType, refField] = optionsReference.split('.');

            optionsPath = `entityDefs.${refEntityType}.fields.${refField}.options`;
        }

        if (optionsPath) {
            this.params.options = Espo.Utils.clone(this.getMetadata().get(optionsPath)) || [];
        }

        this.styleMap = this.params.style || {};

        this.setupOptions();

        if ('translatedOptions' in this.options) {
            this.translatedOptions = this.options.translatedOptions;
        }

        if ('translatedOptions' in this.params) {
            this.translatedOptions = this.params.translatedOptions;
        }

        if (!this.translatedOptions) {
            this.setupTranslation();
        }

        this.displayAsLabel = this.params.displayAsLabel || this.displayAsLabel;
        this.displayAsList = this.params.displayAsList || this.displayAsList;

        if (this.params.isSorted && this.translatedOptions) {
            this.params.options = Espo.Utils.clone(this.params.options);
            this.params.options = this.params.options.sort((v1, v2) => {
                 return (this.translatedOptions[v1] || v1).localeCompare(this.translatedOptions[v2] || v2);
            });
        }

        if (this.options.customOptionList) {
            this.setOptionList(this.options.customOptionList, true);
        }

        if (this.params.allowCustomOptions || !this.params.options) {
            this.allowCustomOptions = true;
        }
    }

    focusOnElement() {
        let $button = this.$el.find('button[data-action="showAddModal"]');

        if ($button[0]) {
            $button[0].focus({preventScroll: true});

            return;
        }

        let $input = this.$el.find('input');

        if ($input[0]) {
            $input[0].focus({preventScroll: true});
        }
    }

    setupSearch() {
        this.events['change select.search-type'] = e => {
            this.handleSearchType($(e.currentTarget).val());
        };
    }

    handleSearchType(type) {
        let $inputContainer = this.$el.find('div.input-container');

        if (~['anyOf', 'noneOf', 'allOf'].indexOf(type)) {
            $inputContainer.removeClass('hidden');
        } else {
            $inputContainer.addClass('hidden');
        }
    }

    setupTranslation() {
        let obj = {};

        let translation = this.params.translation;
        /** @type {?string} */
        let optionsReference = this.params.optionsReference;

        if (!translation && optionsReference) {
            let [refEntityType, refField] = optionsReference.split('.');

            translation = `${refEntityType}.options.${refField}`;
        }

        this.translatedOptions = null;

        if (!this.params.options) {
            return;
        }

        obj = translation ?
            this.getLanguage().translatePath(translation) :
            this.translate(this.name, 'options', this.model.name);

        let map = {};

        this.params.options.forEach(o => {
            if (typeof obj === 'object' && o in obj) {
                map[o] = obj[o];

                return;
            }

            map[o] = o;
        });

        this.translatedOptions = map;
    }

    setupOptions() {}

    setOptionList(optionList, silent) {
        let previousOptions = this.params.options;

        if (!this.originalOptionList) {
            this.originalOptionList = this.params.options;
        }

        this.params.options = Espo.Utils.clone(optionList);

        let isChanged = !_(previousOptions).isEqual(optionList);

        if (this.isEditMode() && !silent && isChanged) {
            let selectedOptionList = [];

            this.selected.forEach(option => {
                if (~optionList.indexOf(option)) {
                    selectedOptionList.push(option);
                }
            });

            this.selected = selectedOptionList;

            if (this.isRendered()) {
                this.reRender();

                this.trigger('change');
            }
            else {
                this.once('after:render', () => {
                    this.trigger('change');
                });
            }
        }
    }

    setTranslatedOptions(translatedOptions) {
        this.translatedOptions = translatedOptions;
    }

    resetOptionList() {
        if (!this.originalOptionList) {
            return;
        }

        let previousOptions = this.params.options;

        this.params.options = Espo.Utils.clone(this.originalOptionList);

        let isChanged = !_(previousOptions).isEqual(this.originalOptionList);

        if (!this.isEditMode() || !isChanged) {
            return;
        }

        if (this.isRendered()) {
            this.reRender();
        }
    }

    controlAddItemButton() {
        let $select = this.$select;

        if (!$select) {
            return;
        }

        if (!$select.get(0)) {
            return;
        }

        let value = $select.val().toString().trim();

        if (!value && this.params.noEmptyString) {
            this.$addButton.addClass('disabled').attr('disabled', 'disabled');
        }
        else {
            this.$addButton.removeClass('disabled').removeAttr('disabled');
        }
    }

    afterRender() {
        if (this.isEditMode()) {
            this.$list = this.$el.find('.list-group');

            let $select = this.$select = this.$el.find('.select');

            if (this.allowCustomOptions) {
                this.$addButton = this.$el.find('button[data-action="addItem"]');

                this.$addButton.on('click', () => {
                    let value = $select.val().toString();

                    this.addValueFromUi(value);

                    this.focusOnElement();
                });

                $select.on('input', () => this.controlAddItemButton());

                $select.on('keydown', e => {
                    let key = Espo.Utils.getKeyFromKeyEvent(e);

                    if (key === 'Enter') {
                        let value = $select.val().toString();

                        this.addValueFromUi(value);
                    }
                });

                this.controlAddItemButton();
            }

            this.$list.sortable({
                stop: () => {
                    this.fetchFromDom();
                    this.trigger('change');
                },
                distance: 5,
                cancel: 'input,textarea,button,select,option,a[role="button"]',
                cursor: 'grabbing',
            });
        }

        if (this.isSearchMode()) {
            this.renderSearch();
        }
    }

    /**
     * @param {string} value
     */
    addValueFromUi(value) {
        value = value.trim();

        if (this.noEmptyString && value === '') {
            return;
        }

        if (this.params.pattern) {
            let helper = new RegExpPattern(this.getMetadata(), this.getLanguage());

            let result = helper.validate(this.params.pattern, value, this.name, this.entityType);

            if (result) {
                setTimeout(() => this.showValidationMessage(result.message, 'input.select'), 10);

                return;
            }
        }

        this.addValue(value);

        this.$select.val('');

        this.controlAddItemButton();
    }

    renderSearch() {
        this.$element = this.$el.find('.main-element');

        let valueList = this.getSearchParamsData().valueList || this.searchParams.valueFront || [];

        this.$element.val(valueList.join(this.itemDelimiter));

        let items = [];

        (this.params.options || []).forEach(value => {
            let label = this.getLanguage().translateOption(value, this.name, this.scope);

            if (this.translatedOptions) {
                if (value in this.translatedOptions) {
                    label = this.translatedOptions[value];
                }
            }

            if (label === '') {
                return;
            }

            items.push({
                value: value,
                text: label,
            });
        });

        valueList
            .filter(item => !(this.params.options || []).includes(item))
            .forEach(item => {
                items.push({
                    value: item,
                    text: item,
                });
            });

        /** @type {module:ui/multi-select~Options} */
        let multiSelectOptions = {
            items: items,
            delimiter: this.itemDelimiter,
            matchAnyWord: this.matchAnyWord,
            allowCustomOptions: this.allowCustomOptions,
            create: input => {
                return {
                    value: input,
                    text: input,
                };
            },
        };

        MultiSelect.init(this.$element, multiSelectOptions);

        this.$el.find('.selectize-dropdown-content').addClass('small');

        let type = this.$el.find('select.search-type').val();

        this.handleSearchType(type);

        this.$el.find('select.search-type').on('change', () => {
            this.trigger('change');
        });

        this.$element.on('change', () => {
            this.trigger('change');
        });
    }

    fetchFromDom() {
        let selected = [];

        this.$el.find('.list-group .list-group-item').each((i, el) => {
            let value = $(el).attr('data-value').toString();

            selected.push(value);
        });

        this.selected = selected;
    }

    getValueForDisplay() {
        // Do not use the `html` method to avoid XSS.

        /** @var {string[]} */
        let list = this.selected.map(item => {
            let label = null;

            if (this.translatedOptions !== null) {
                if (item in this.translatedOptions) {
                    label = this.translatedOptions[item];
                }
            }

            if (label === null) {
                label = item;
            }

            if (label === '') {
                label = this.translate('None');
            }

            let style = this.styleMap[item] || 'default';

            if (this.params.displayAsLabel) {
                return $('<span>')
                    .addClass('label label-md label-' + style)
                    .text(label)
                    .get(0).outerHTML;

            }

            if (style && style !== 'default') {
                return $('<span>')
                    .addClass('text-' + style)
                    .text(label)
                    .get(0).outerHTML;
            }

            return $('<span>')
                .text(label)
                .get(0).outerHTML;
        });

        if (this.displayAsList) {
            if (!list.length) {
                return '';
            }

            let itemClassName = 'multi-enum-item-container';

            if (this.displayAsLabel) {
                itemClassName += ' multi-enum-item-label-container';
            }

            return list
                .map(item =>
                    $('<div>')
                        .addClass(itemClassName)
                        .html(item)
                        .get(0).outerHTML
                )
                .join('');
        }

        if (this.displayAsLabel) {
            return list.join(' ');
        }

        return list.join(', ');
    }

    getItemHtml(value) {
        // Do not use the `html` method to avoid XSS.

        if (this.translatedOptions !== null) {
            for (let item in this.translatedOptions) {
                if (this.translatedOptions[item] === value) {
                    value = item;

                    break;
                }
            }
        }

        value = value.toString();

        let text = this.translatedOptions && value in this.translatedOptions ?
            this.translatedOptions[value].toString() :
            value;

        return $('<div>')
            .addClass('list-group-item')
            .attr('data-value', value)
            .css('cursor', 'default')
            .append(
                $('<a>')
                    .attr('role', 'button')
                    .attr('tabindex', '0')
                    .addClass('pull-right')
                    .attr('data-value', value)
                    .attr('data-action', 'removeValue')
                    .append(
                        $('<span>').addClass('fas fa-times')
                    )
            )
            .append(
                $('<span>')
                    .addClass('text')
                    .text(text)
            )
            .append('')
            .get(0)
            .outerHTML;
    }

    addValue(value) {
        if (this.selected.indexOf(value) === -1) {
            let html = this.getItemHtml(value);

            this.$list.append(html);
            this.selected.push(value);
            this.trigger('change');
        }
    }

    removeValue(value) {
        let valueInternal = value.replace(/"/g, '\\"');

        this.$list.children('[data-value="' + valueInternal + '"]').remove();

        let index = this.selected.indexOf(value);

        this.selected.splice(index, 1);
        this.trigger('change');
    }

    fetch() {
        let data = {};

        let list = Espo.Utils.clone(this.selected || []);

        if (this.params.isSorted && this.translatedOptions) {
            list = list.sort((v1, v2) => {
                 return (this.translatedOptions[v1] || v1)
                     .localeCompare(this.translatedOptions[v2] || v2);
            });
        }

        data[this.name] = list;

        return data;
    }

    fetchSearch() {
        let type = this.$el.find('select.search-type').val() || 'anyOf';

        let valueList;

        if (~['anyOf', 'noneOf', 'allOf'].indexOf(type)) {
            valueList = this.$element.val().split(this.itemDelimiter);

            if (valueList.length === 1 && valueList[0] === '') {
                valueList = [];
            }

            if (valueList.length === 0) {
               if (type === 'anyOf') {
                   return {
                       type: 'any',
                       data: {
                           type: type,
                           valueList: valueList,
                       },
                   };
               }

               if (type === 'noneOf') {
                   return {
                       type: 'any',
                       data: {
                           type: type,
                           valueList: valueList,
                       },
                   };
               }

               if (type === 'allOf') {
                   return {
                       type: 'any',
                       data: {
                           type: type,
                           valueList: valueList,
                       },
                   };
               }
           }
        }

        if (type === 'anyOf') {
            let data = {
                type: 'arrayAnyOf',
                value: valueList,
                data: {
                    type: 'anyOf',
                    valueList: valueList,
                },
            };

            if (!valueList.length) {
                data.value = null;
            }

            return data;
        }

        if (type === 'noneOf') {
            return {
                type: 'arrayNoneOf',
                value: valueList,
                data: {
                    type: 'noneOf',
                    valueList: valueList,
                },
            };
        }

        if (type === 'allOf') {
            let data = {
                type: 'arrayAllOf',
                value: valueList,
                data: {
                    type: 'allOf',
                    valueList: valueList,
                },
            };

            if (!valueList.length) {
                data.value = null;
            }

            return data;
        }

        if (type === 'isEmpty') {
            return {
                type: 'arrayIsEmpty',
                data: {
                    type: 'isEmpty',
                },
            };
        }

        if (type === 'isNotEmpty') {
            return {
                type: 'arrayIsNotEmpty',
                data: {
                    type: 'isNotEmpty',
                },
            };
        }

        return null;
    }

    validateRequired() {
        if (this.isRequired()) {
            let value = this.model.get(this.name);

            if (!value || value.length === 0) {
                let msg = this.translate('fieldIsRequired', 'messages')
                    .replace('{field}', this.getLabelText());

                this.showValidationMessage(msg, '.array-control-container');

                return true;
            }
        }

        return false;
    }

    validateMaxCount() {
        if (this.params.maxCount) {
            let itemList = this.model.get(this.name) || [];

            if (itemList.length > this.params.maxCount) {
                let msg =
                    this.translate('fieldExceedsMaxCount', 'messages')
                        .replace('{field}', this.getLabelText())
                        .replace('{maxCount}', this.params.maxCount.toString());

                this.showValidationMessage(msg, '.array-control-container');

                return true;
            }
        }

        return false;
    }

    getSearchType() {
        return this.getSearchParamsData().type || 'anyOf';
    }

    /**
     * @return {{
     *    translatedOptions: Object.<string, *>|null,
     *    options: string[],
     * } | Object.<string, *>}
     */
    getAddItemModalOptions() {
        let options = [];

        this.params.options.forEach(item => {
            if (!~this.selected.indexOf(item)) {
                options.push(item);
            }
        });

        return {
            options: options,
            translatedOptions: this.translatedOptions,
        };
    }

    actionAddItem() {
        this.createView('addModal', this.addItemModalView, this.getAddItemModalOptions(), view => {
            view.render();

            view.once('add', item => {
                this.addValue(item);
                view.close();
            });

            view.once('add-mass', items => {
                items.forEach(item => this.addValue(item));
                view.close();
            });
        });
    }
}

export default ArrayFieldView;
PK]yQ�
aa views/fields/foreign-datetime.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import DatetimeFieldView from 'views/fields/datetime';
import Helper from 'helpers/misc/foreign-field';

class ForeignDatetimeFieldView extends DatetimeFieldView {

    type = 'foreign'

    setup() {
        super.setup();

        const helper = new Helper(this);

        const foreignParams = helper.getForeignParams();

        for (let param in foreignParams) {
            this.params[param] = foreignParams[param];
        }
    }
}

export default ForeignDatetimeFieldView;
PK]�n�c��$views/fields/foreign-url-multiple.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import UrlMultipleFieldView from 'views/fields/url-multiple';
import Helper from 'helpers/misc/foreign-field';

class ForeignUrlMultipleFieldView extends UrlMultipleFieldView {

    type = 'foreign'
    readOnly = true

    setup() {
        super.setup();

        const helper = new Helper(this);

        const foreignParams = helper.getForeignParams();

        for (let param in foreignParams) {
            this.params[param] = foreignParams[param];
        }
    }
}

export default ForeignUrlMultipleFieldView;
PK]��J�{{views/fields/multi-enum.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/fields/multi-enum */

import ArrayFieldView from 'views/fields/array';
import RegExpPattern from 'helpers/reg-exp-pattern';
import MultiSelect from 'ui/multi-select';

/**
 * A multi-enum field.
 */
class MultiEnumFieldView extends ArrayFieldView {

    type = 'multiEnum'

    listTemplate = 'fields/array/list'
    detailTemplate = 'fields/array/detail'
    editTemplate = 'fields/multi-enum/edit'

    /** @const */
    MAX_ITEM_LENGTH = 100

    /**
     * @protected
     * @type {boolean}
     */
    restoreOnBackspace = false

    validationElementSelector = '.selectize-control'

    events = {}

    /** @inheritDoc */
    data() {
        return {
            ...super.data(),
            optionList: this.params.options || [],
        };
    }

    getTranslatedOptions() {
        return (this.params.options || []).map(item => {
            if (this.translatedOptions !== null) {
                if (item in this.translatedOptions) {
                    return this.translatedOptions[item];
                }
            }

            return item;
        });
    }

    translateValueToEditLabel(value) {
        let label = value;

        if (~(this.params.options || []).indexOf(value)) {
            label = this.getLanguage().translateOption(value, this.name, this.scope);
        }

        if (this.translatedOptions) {
            if (value in this.translatedOptions) {
                label = this.translatedOptions[value];
            }
        }

        if (label === '') {
            label = this.translate('None');
        }

        return label;
    }

    afterRender() {
        if (this.isSearchMode()) {
            this.renderSearch();

            return;
        }

        if (this.isEditMode()) {
            this.$element = this.$el.find('[data-name="' + this.name + '"]');

            let items = [];
            let valueList = Espo.Utils.clone(this.selected);

            for (let i in valueList) {
                let value = valueList[i];
                let originalValue = value;

                if (value === '') {
                    value = valueList[i] = '__emptystring__';
                }

                if (!~(this.params.options || []).indexOf(value)) {
                    items.push({
                        value: value,
                        text: this.translateValueToEditLabel(originalValue),
                    });
                }
            }

            this.$element.val(valueList.join(this.itemDelimiter));

            (this.params.options || []).forEach(value => {
                let originalValue = value;

                if (value === '') {
                    value = '__emptystring__';
                }

                items.push({
                    value: value,
                    text: this.translateValueToEditLabel(originalValue),
                });
            });

            /** @type {module:ui/multi-select~Options} */
            let multiSelectOptions = {
                items: items,
                delimiter: this.itemDelimiter,
                matchAnyWord: this.matchAnyWord,
                draggable: true,
                allowCustomOptions: this.allowCustomOptions,
                restoreOnBackspace: this.restoreOnBackspace,
                create: input => this.createCustomOptionCallback(input),
            };

            MultiSelect.init(this.$element, multiSelectOptions);

            this.$element.on('change', () => {
                this.trigger('change');
            });
        }
    }

    /**
     * @protected
     * @param {string} input
     * @return {{text: string, value: string}|null}
     */
    createCustomOptionCallback(input) {
        if (input.length > this.MAX_ITEM_LENGTH) {
            let message = this.translate('arrayItemMaxLength', 'messages')
                .replace('{max}', this.MAX_ITEM_LENGTH.toString())

            this.showValidationMessage(message, '.selectize-control')

            return null;
        }

        if (this.params.pattern) {
            let helper = new RegExpPattern(this.getMetadata(), this.getLanguage());

            let result = helper.validate(this.params.pattern, input, this.name, this.entityType);

            if (result) {
                this.showValidationMessage(result.message, '.selectize-control')

                return null;
            }
        }

        return {
            value: input,
            text: input,
        };
    }

    focusOnInlineEdit() {
        MultiSelect.focus(this.$element);
    }

    fetch() {
        let list = this.$element.val().split(this.itemDelimiter);

        if (list.length === 1 && list[0] === '') {
            list = [];
        }

        for (let i in list) {
            if (list[i] === '__emptystring__') {
                list[i] = '';
            }
        }

        if (this.params.isSorted && this.translatedOptions) {
            list = list.sort((v1, v2) => {
                 return (this.translatedOptions[v1] || v1)
                     .localeCompare(this.translatedOptions[v2] || v2);
            });
        }

        let data = {};

        data[this.name] = list;

        return data;
    }

    validateRequired() {
        if (!this.isRequired()) {
            return;
        }

        let value = this.model.get(this.name);

        if (!value || value.length === 0) {
            let msg = this.translate('fieldIsRequired', 'messages')
                .replace('{field}', this.getLabelText());

            this.showValidationMessage(msg, '.selectize-control');

            return true;
        }
    }

    validateMaxCount() {
        if (!this.params.maxCount) {
            return;
        }

        let itemList = this.model.get(this.name) || [];

        if (itemList.length > this.params.maxCount) {
            let msg =
                this.translate('fieldExceedsMaxCount', 'messages')
                    .replace('{field}', this.getLabelText())
                    .replace('{maxCount}', this.params.maxCount.toString());

            this.showValidationMessage(msg, '.selectize-control');

            return true;
        }
    }
}

export default MultiEnumFieldView;
PK]�[q�LLviews/fields/json-object.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import BaseFieldView from 'views/fields/base';

class JsonObjectFieldView extends BaseFieldView {

    type = 'jsonObject'

    listTemplate = 'fields/json-object/detail'
    detailTemplate = 'fields/json-object/detail'

    data() {
        const data = super.data();

        data.valueIsSet = this.model.has(this.name);
        data.isNotEmpty = !!this.model.get(this.name);

        return data;
    }

    getValueForDisplay() {
        const value = this.model.get(this.name);

        if (!value) {
            return null;
        }

        return JSON.stringify(value, null, 2)
            .replace(/(\r\n|\n|\r)/gm, '<br>').replace(/\s/g, '&nbsp;');
    }
}

export default JsonObjectFieldView;

PK]���oRRviews/fields/foreign-float.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import FloatFieldView from 'views/fields/float';
import Helper from 'helpers/misc/foreign-field';

class ForeignFloatFieldView extends FloatFieldView {

    type = 'foreign'

    setup() {
        super.setup();

        const helper = new Helper(this);

        const foreignParams = helper.getForeignParams();

        for (let param in foreignParams) {
            this.params[param] = foreignParams[param];
        }
    }
}

export default ForeignFloatFieldView;
PK]�|���views/fields/link-one.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import LinkFieldView from 'views/fields/link';

class LinkOneFieldView extends LinkFieldView {

    searchTypeList = ['is', 'isEmpty', 'isNotEmpty', 'isOneOf']

    fetchSearch() {
        let type = this.$el.find('select.search-type').val();
        let value = this.$el.find('[data-name="' + this.idName + '"]').val();

        if (type === 'isOneOf') {
            return  {
                type: 'linkedWith',
                field: this.name,
                value: this.searchData.oneOfIdList,
                data: {
                    type: type,
                    oneOfIdList: this.searchData.oneOfIdList,
                    oneOfNameHash: this.searchData.oneOfNameHash,
                },
            };
        }
        else if (type === 'is' || !type) {
            if (!value) {
                return false;
            }

            return  {
                type: 'linkedWith',
                field: this.name,
                value: value,
                data: {
                    type: type,
                    nameValue: this.$el.find('[data-name="' + this.nameName + '"]').val(),
                },
            };
        }
        else if (type === 'isEmpty') {
            return  {
                type: 'isNotLinked',
                data: {
                    type: type,
                },
            };
        }
        else if (type === 'isNotEmpty') {
            return  {
                type: 'isLinked',
                data: {
                    type: type,
                },
            };
        }
    }
}

export default LinkOneFieldView;
PK]i��views/fields/enum-styled.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import EnumFieldView from 'views/fields/enum';

class EnumStyledFieldView extends EnumFieldView {}

// noinspection JSUnusedGlobalSymbols
export default EnumStyledFieldView;
PK]U��4ssviews/fields/image.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import FileFieldView from 'views/fields/file';

class ImageFieldView extends FileFieldView {

    type = 'image'

    showPreview = true
    accept = ['image/*']
    defaultType = 'image/jpeg'
    previewSize = 'small'
}

export default ImageFieldView;
PK]���H�H�views/fields/link.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/fields/link */

import BaseFieldView from 'views/fields/base';
import RecordModal from 'helpers/record-modal';

/**
 * A link field (belongs-to relation).
 */
class LinkFieldView extends BaseFieldView {

    /** @inheritDoc */
    type = 'link'

    /** @inheritDoc */
    listTemplate = 'fields/link/list'
    /** @inheritDoc */
    detailTemplate = 'fields/link/detail'
    /** @inheritDoc */
    editTemplate = 'fields/link/edit'
    /** @inheritDoc */
    searchTemplate = 'fields/link/search'

    /**
     * A name attribute name.
     *
     * @type {string}
     */
    nameName

    /**
     * An ID attribute name.
     *
     * @type {string}
     */
    idName

    /**
     * A foreign entity type.
     *
     * @type {string|null}
     */
    foreignScope = null

    /**
     * A select-record view.
     *
     * @protected
     * @type {string}
     */
    selectRecordsView = 'views/modals/select-records'

    /**
     * Autocomplete disabled.
     *
     * @protected
     * @type {boolean}
     */
    autocompleteDisabled = false

    /**
     * Create disabled.
     *
     * @protected
     * @type {boolean}
     */
    createDisabled = false

    /**
     * To display the create button.
     *
     * @protected
     * @type {boolean}
     */
    createButton = false

    /**
     * Force create button even is disabled in clientDefs > relationshipPanels.
     *
     * @protected
     * @type {boolean}
     */
    forceCreateButton = false

    /**
     * A search type list.
     *
     * @protected
     * @type {string[]}
     */
    searchTypeList = [
        'is',
        'isEmpty',
        'isNotEmpty',
        'isNot',
        'isOneOf',
        'isNotOneOf',
    ]

    /**
     * A primary filter list that will be available when selecting a record.
     *
     * @protected
     * @type {string[]|null}
     */
    selectFilterList = null

    /**
     * A select primary filter.
     *
     * @protected
     * @type {string|null}
     */
    selectPrimaryFilterName = null

    /**
     * A select bool filter list.
     *
     * @protected
     * @type {string[]|null}
     */
    selectBoolFilterList = null

    /**
     * An autocomplete max record number.
     *
     * @protected
     * @type {number|null}
     */
    autocompleteMaxCount = null

    /**
     * Select all attributes.
     *
     * @protected
     * @type {boolean}
     */
    forceSelectAllAttributes = false

    /**
     * @protected
     * @type {string[]|null}
     */
    mandatorySelectAttributeList = null

    /**
     * Trigger autocomplete on empty input.
     *
     * @protected
     * @type {boolean}
     */
    autocompleteOnEmpty = false

    /** @inheritDoc */
    events = {
        /** @this LinkFieldView */
        'auxclick a[href]:not([role="button"])': function (e) {
            if (!this.isReadMode()) {
                return;
            }

            let isCombination = e.button === 1 && (e.ctrlKey || e.metaKey);

            if (!isCombination) {
                return;
            }

            e.preventDefault();
            e.stopPropagation();

            this.quickView();
        },
    }

    /** @inheritDoc */
    data() {
        let nameValue = this.model.has(this.nameName) ?
            this.model.get(this.nameName) :
            this.model.get(this.idName);

        if (nameValue === null) {
            nameValue = this.model.get(this.idName);
        }

        if (this.isReadMode() && !nameValue && this.model.get(this.idName)) {
            nameValue = this.translate(this.foreignScope, 'scopeNames');
        }

        let iconHtml = null;

        if (this.isDetailMode()) {
            iconHtml = this.getHelper().getScopeColorIconHtml(this.foreignScope);
        }

        const createButton = this.createButton && (!this.createDisabled || this.forceCreateButton);

        return {
            ...super.data(),
            idName: this.idName,
            nameName: this.nameName,
            idValue: this.model.get(this.idName),
            nameValue: nameValue,
            foreignScope: this.foreignScope,
            valueIsSet: this.model.has(this.idName),
            iconHtml: iconHtml,
            url: this.getUrl(),
            createButton: createButton,
        };
    }

    /**
     * @protected
     * @return {?string}
     */
    getUrl() {
        let id = this.model.get(this.idName);

        if (!id) {
            return null;
        }

        return '#' + this.foreignScope + '/view/' + id;
    }

    /**
     * Get advanced filters (field filters) to be applied when select a record.
     * Can be extended.
     *
     * @protected
     * @return {Object.<string,module:search-manager~advancedFilter>|null}
     */
    getSelectFilters() {
        return null;
    }

    /**
     * Get a select bool filter list. Applied when select a record.
     * Can be extended.
     *
     * @protected
     * @return {string[]|null}
     */
    getSelectBoolFilterList() {
        return this.selectBoolFilterList;
    }

    /**
     * Get a select primary filter. Applied when select a record.
     * Can be extended.
     *
     * @protected
     * @return {string|null}
     */
    getSelectPrimaryFilterName() {
        return this.selectPrimaryFilterName;
    }

    /**
     * Get a primary filter list that will be available when selecting a record.
     * Can be extended.
     *
     * @return {string[]|null}
     */
    getSelectFilterList() {
        return this.selectFilterList;
    }

    /**
     * Attributes to pass to a model when creating a new record.
     * Can be extended.
     *
     * @return {Object.<string,*>|null}
     */
    getCreateAttributes() {
        let attributeMap = this.getMetadata()
            .get(['clientDefs', this.entityType, 'relationshipPanels', this.name, 'createAttributeMap']) || {};

        let attributes = {};

        Object.keys(attributeMap).forEach(attr => attributes[attributeMap[attr]] = this.model.get(attr));

        return attributes;
    }

    /** @inheritDoc */
    setup() {
        this.nameName = this.name + 'Name';
        this.idName = this.name + 'Id';

        this.foreignScope = this.options.foreignScope || this.foreignScope;

        this.foreignScope = this.foreignScope ||
            this.model.getFieldParam(this.name, 'entity') || this.model.getLinkParam(this.name, 'entity');

        if ('createDisabled' in this.options) {
            this.createDisabled = this.options.createDisabled;
        }

        if (!this.isListMode()) {
            this.addActionHandler('selectLink', () => this.actionSelect());
            this.addActionHandler('clearLink', () => this.clearLink());
        }

        if (this.isSearchMode()) {
            this.addActionHandler('selectLinkOneOf', () => this.actionSelectOneOf());

            this.events['click a[data-action="clearLinkOneOf"]'] = e =>{
                let id = $(e.currentTarget).data('id').toString();

                this.deleteLinkOneOf(id);
            };
        }

        if (this.createButton) {
            this.addActionHandler('createLink', () => this.actionCreateLink());
        }

        /** @type {Object.<string, *>} */
        this.panelDefs = this.getMetadata()
            .get(['clientDefs', this.entityType, 'relationshipPanels', this.name]) || {};

        if (this.panelDefs.createDisabled) {
            this.createDisabled = true;
        }
    }

    /**
     * Select.
     *
     * @param {module:model} model A model.
     * @protected
     */
    select(model) {
        this.$elementName.val(model.get('name') || model.id);
        this.$elementId.val(model.get('id'));

        if (this.mode === this.MODE_SEARCH) {
            this.searchData.idValue = model.get('id');
            this.searchData.nameValue = model.get('name') || model.id;
        }

        this.trigger('change');

        this.getSelectFieldHandler().then(handler => {
            handler.getAttributes(model)
                .then(attributes => {
                    this.model.set(attributes)
                });
        });
    }

    /**
     * Clear.
     */
    clearLink() {
        this.$elementName.val('');
        this.$elementId.val('');

        this.trigger('change');

        this.getSelectFieldHandler().then(handler => {
            handler.getClearAttributes()
                .then(attributes => {
                    this.model.set(attributes)
                });
        });
    }

    /**
     * @private
     * @return {Promise<{
     *     getAttributes: function (module:model): Promise<Object.<string, *>>,
     *     getClearAttributes: function(): Promise<Object.<string, *>>,
     * }>}
     */
    getSelectFieldHandler() {
        if (!this.panelDefs.selectFieldHandler) {
            return Promise.resolve({
                getClearAttributes: () => Promise.resolve({}),
                getAttributes: () => Promise.resolve({}),
            });
        }

        return new Promise(resolve => {
            Espo.loader.requirePromise(this.panelDefs.selectFieldHandler)
                .then(Handler => {
                    const handler = new Handler(this.getHelper());

                    resolve(handler);
                });
        });
    }

    /** @inheritDoc */
    setupSearch() {
        this.searchData.oneOfIdList = this.getSearchParamsData().oneOfIdList ||
            this.searchParams.oneOfIdList || [];

        this.searchData.oneOfNameHash = this.getSearchParamsData().oneOfNameHash ||
            this.searchParams.oneOfNameHash || {};

        if (~['is', 'isNot', 'equals'].indexOf(this.getSearchType())) {
            this.searchData.idValue = this.getSearchParamsData().idValue ||
                this.searchParams.idValue || this.searchParams.value;

            this.searchData.nameValue = this.getSearchParamsData().nameValue ||
                this.searchParams.nameValue || this.searchParams.valueName;
        }

        this.events['change select.search-type'] = e => {
            let type = $(e.currentTarget).val();

            this.handleSearchType(type);
        };
    }

    /**
     * Handle a search type.
     *
     * @protected
     * @param {string} type A type.
     */
    handleSearchType(type) {
        if (~['is', 'isNot', 'isNotAndIsNotEmpty'].indexOf(type)) {
            this.$el.find('div.primary').removeClass('hidden');
        }
        else {
            this.$el.find('div.primary').addClass('hidden');
        }

        if (~['isOneOf', 'isNotOneOf', 'isNotOneOfAndIsNotEmpty'].indexOf(type)) {
            this.$el.find('div.one-of-container').removeClass('hidden');
        }
        else {
            this.$el.find('div.one-of-container').addClass('hidden');
        }
    }

    /**
     * Get an autocomplete max record number. Can be extended.
     *
     * @protected
     * @return {number}
     */
    getAutocompleteMaxCount() {
        if (this.autocompleteMaxCount) {
            return this.autocompleteMaxCount;
        }

        return this.getConfig().get('recordsPerPage');
    }

    /**
     * Compose an autocomplete URL. Can be extended.
     *
     * @protected
     * @return {string|Promise<string>}
     */
    getAutocompleteUrl() {
        let url = this.foreignScope + '?maxSize=' + this.getAutocompleteMaxCount();

        if (!this.forceSelectAllAttributes) {
            const mandatorySelectAttributeList = this.mandatorySelectAttributeList ||
                this.panelDefs.selectMandatoryAttributeList;

            let select = ['id', 'name'];

            if (mandatorySelectAttributeList) {
                select = select.concat(mandatorySelectAttributeList);
            }

            url += '&select=' + select.join(',');
        }

        if (this.panelDefs.selectHandler) {
            return new Promise(resolve => {
                this._getSelectFilters().then(filters => {
                    if (filters.bool) {
                        url += '&' + $.param({'boolFilterList': filters.bool});
                    }

                    if (filters.primary) {
                        url += '&' + $.param({'primaryFilter': filters.primary});
                    }

                    if (filters.advanced) {
                        url += '&' + $.param({'where': filters.advanced});
                    }

                    resolve(url);
                });
            });
        }

        const boolList = [
            ...(this.getSelectBoolFilterList() || []),
            ...(this.panelDefs.selectBoolFilterList || []),
        ];

        const primary = this.getSelectPrimaryFilterName() || this.panelDefs.selectPrimaryFilterName;

        if (boolList.length) {
            url += '&' + $.param({'boolFilterList': boolList});
        }

        if (primary) {
            url += '&' + $.param({'primaryFilter': primary});
        }

        return url;
    }

    /** @inheritDoc */
    afterRender() {
        if (this.isEditMode() || this.isSearchMode()) {
            this.$elementId = this.$el.find('input[data-name="' + this.idName + '"]');
            this.$elementName = this.$el.find('input[data-name="' + this.nameName + '"]');

            this.$elementName.on('change', () => {
                if (this.$elementName.val() === '') {
                    this.clearLink();
                }
            });

            this.$elementName.on('blur', e => {
                setTimeout(() => {
                    if (this.mode === this.MODE_EDIT && this.model.has(this.nameName)) {
                        e.currentTarget.value = this.model.get(this.nameName);
                    }
                }, 100);

                if (!this.autocompleteDisabled) {
                    setTimeout(() => this.$elementName.autocomplete('clear'), 300);
                }
            });

            let $elementName = this.$elementName;

            if (!this.autocompleteDisabled) {
                let isEmptyQueryResult = false;

                if (this.getEmptyAutocompleteResult()) {
                    this.$elementName.on('keydown', e => {
                        if (e.code === 'Tab' && isEmptyQueryResult) {
                            e.stopImmediatePropagation();
                        }
                    });
                }

                this.$elementName.autocomplete({
                    beforeRender: $c => {
                        if (this.$elementName.hasClass('input-sm')) {
                            $c.addClass('small');
                        }
                    },
                    lookup: (q, callback) => {
                        if (!this.autocompleteOnEmpty && q.length === 0) {
                            isEmptyQueryResult = true;

                            const emptyResult = this.getEmptyAutocompleteResult();

                            if (emptyResult) {
                                callback(this._transformAutocompleteResult(emptyResult));
                            }

                            return;
                        }

                        isEmptyQueryResult = false;

                        Promise.resolve(this.getAutocompleteUrl(q))
                            .then(url => {
                                Espo.Ajax
                                    .getRequest(url, {q: q})
                                    .then(response => {
                                        callback(this._transformAutocompleteResult(response));
                                    });
                            });
                    },
                    minChars: 0,
                    triggerSelectOnValidInput: false,
                    autoSelectFirst: true,
                    noCache: true,
                    formatResult: suggestion => {
                        return this.getHelper().escapeString(suggestion.name);
                    },
                    onSelect: s => {
                        this.getModelFactory().create(this.foreignScope, (model) => {
                            model.set(s.attributes);

                            this.select(model);

                            this.$elementName.focus();
                        });
                    },
                });

                this.$elementName.off('focus.autocomplete');

                this.$elementName.on('focus', () => {
                    if (this.$elementName.val()) {
                        this.$elementName.get(0).select();

                        return;
                    }

                    this.$elementName.autocomplete('onFocus');
                });

                this.$elementName.attr('autocomplete', 'espo-' + this.name);

                this.once('render', () => {
                    $elementName.autocomplete('dispose');
                });

                this.once('remove', () => {
                    $elementName.autocomplete('dispose');
                });

                if (this.isSearchMode()) {
                    let $elementOneOf = this.$el.find('input.element-one-of');

                    $elementOneOf.autocomplete({
                        beforeRender: $c => {
                            if (this.$elementName.hasClass('input-sm')) {
                                $c.addClass('small');
                            }
                        },
                        serviceUrl: () => {
                            return this.getAutocompleteUrl();
                        },
                        minChars: 1,
                        paramName: 'q',
                        noCache: true,
                        formatResult: suggestion => {
                            // noinspection JSUnresolvedReference
                            return this.getHelper().escapeString(suggestion.name);
                        },
                        transformResult: response => {
                            return this._transformAutocompleteResult(JSON.parse(response));
                        },
                        onSelect: s => {
                            this.getModelFactory().create(this.foreignScope, model => {
                                // noinspection JSUnresolvedReference
                                model.set(s.attributes);

                                this.selectOneOf([model]);

                                $elementOneOf.val('');
                                setTimeout(() => $elementOneOf.focus(), 50);
                            });
                        },
                    });

                    $elementOneOf.attr('autocomplete', 'espo-' + this.name);

                    this.once('render', () => {
                        $elementOneOf.autocomplete('dispose');
                    });

                    this.once('remove', () => {
                        $elementOneOf.autocomplete('dispose');
                    });

                    this.$el.find('select.search-type').on('change', () => {
                        this.trigger('change');
                    });
                }
            }

            $elementName.on('change', () => {
                if (!this.isSearchMode() && !this.model.get(this.idName)) {
                    $elementName.val(this.model.get(this.nameName));
                }
            });
        }

        if (this.isSearchMode()) {
            var type = this.$el.find('select.search-type').val();

            this.handleSearchType(type);

            if (~['isOneOf', 'isNotOneOf', 'isNotOneOfAndIsNotEmpty'].indexOf(type)) {
                this.searchData.oneOfIdList.forEach(id => {
                    this.addLinkOneOfHtml(id, this.searchData.oneOfNameHash[id]);
                });
            }
        }
    }

    /**
     * @private
     */
    _transformAutocompleteResult(response) {
        const list = [];

        response.list.forEach(item => {
            list.push({
                id: item.id,
                name: item.name || item.id,
                data: item.id,
                value: item.name || item.id,
                attributes: item,
            });
        });

        return {suggestions: list};
    }

    /** @inheritDoc */
    getValueForDisplay() {
        return this.model.get(this.nameName);
    }

    /** @inheritDoc */
    validateRequired() {
        if (this.isRequired()) {
            if (this.model.get(this.idName) == null) {
                var msg = this.translate('fieldIsRequired', 'messages')
                    .replace('{field}', this.getLabelText());

                this.showValidationMessage(msg);

                return true;
            }
        }
    }

    /**
     * Delete a one-of item. For search mode.
     *
     * @param {string} id An ID.
     */
    deleteLinkOneOf(id) {
        this.deleteLinkOneOfHtml(id);

        var index = this.searchData.oneOfIdList.indexOf(id);

        if (index > -1) {
            this.searchData.oneOfIdList.splice(index, 1);
        }

        delete this.searchData.oneOfNameHash[id];

        this.trigger('change');
    }

    /**
     * Add a one-of item. For search mode.
     *
     * @param {string} id An ID.
     * @param {string} name A name.
     */
    addLinkOneOf(id, name) {
        if (!~this.searchData.oneOfIdList.indexOf(id)) {
            this.searchData.oneOfIdList.push(id);
            this.searchData.oneOfNameHash[id] = name;
            this.addLinkOneOfHtml(id, name);

            this.trigger('change');
        }
    }

    /**
     * @protected
     * @param {string} id An ID.
     */
    deleteLinkOneOfHtml(id) {
        this.$el.find('.link-one-of-container .link-' + id).remove();
    }

    /**
     * @protected
     * @param {string} id An ID.
     * @param {string} name A name.
     * @return {JQuery}
     */
    addLinkOneOfHtml(id, name) {
        let $container = this.$el.find('.link-one-of-container');

        let $el = $('<div>')
            .addClass('link-' + id)
            .addClass('list-group-item');

        $el.append(
            $('<a>')
                .attr('role', 'button')
                .addClass('pull-right')
                .attr('data-id', id)
                .attr('data-action', 'clearLinkOneOf')
                .append(
                    $('<span>').addClass('fas fa-times')
                ),
            $('<span>').text(name),
            ' '
        );

        $container.append($el);

        return $el;
    }

    /** @inheritDoc */
    fetch() {
        var data = {};

        data[this.nameName] = this.$el.find('[data-name="'+this.nameName+'"]').val() || null;
        data[this.idName] = this.$el.find('[data-name="'+this.idName+'"]').val() || null;

        return data;
    }

    /** @inheritDoc */
    fetchSearch() {
        var type = this.$el.find('select.search-type').val();
        var value = this.$el.find('[data-name="' + this.idName + '"]').val();

        if (~['isOneOf', 'isNotOneOf'].indexOf(type) && !this.searchData.oneOfIdList.length) {
            return {
                type: 'isNotNull',
                attribute: 'id',
                data: {
                    type: type,
                },
            };
        }

        if (type === 'isEmpty') {
            return {
                type: 'isNull',
                attribute: this.idName,
                data: {
                    type: type,
                }
            };
        }

        if (type === 'isNotEmpty') {
            return {
                type: 'isNotNull',
                attribute: this.idName,
                data: {
                    type: type,
                },
            };
        }

        if (type === 'isOneOf') {
            return {
                type: 'in',
                attribute: this.idName,
                value: this.searchData.oneOfIdList,
                data: {
                    type: type,
                    oneOfIdList: this.searchData.oneOfIdList,
                    oneOfNameHash: this.searchData.oneOfNameHash,
                },
            };
        }

        if (type === 'isNotOneOf') {
            return {
                type: 'or',
                value: [
                    {
                        type: 'notIn',
                        attribute: this.idName,
                        value: this.searchData.oneOfIdList,
                    },
                    {
                        type: 'isNull',
                        attribute: this.idName,
                    },
                ],
                data: {
                    type: type,
                    oneOfIdList: this.searchData.oneOfIdList,
                    oneOfNameHash: this.searchData.oneOfNameHash,
                }
            };
        }

        if (type === 'isNotOneOfAndIsNotEmpty') {
            return {
                type: 'notIn',
                attribute: this.idName,
                value: this.searchData.oneOfIdList,
                data: {
                    type: type,
                    oneOfIdList: this.searchData.oneOfIdList,
                    oneOfNameHash: this.searchData.oneOfNameHash,
                },
            };
        }

        if (type === 'isNot') {
            if (!value) {
                return false;
            }

            let nameValue = this.$el.find('[data-name="' + this.nameName + '"]').val();

            return {
                type: 'or',
                value: [
                    {
                        type: 'notEquals',
                        attribute: this.idName,
                        value: value
                    },
                    {
                        type: 'isNull',
                        attribute: this.idName,
                    }
                ],
                data: {
                    type: type,
                    idValue: value,
                    nameValue: nameValue,
                }
            };
        }

        if (type === 'isNotAndIsNotEmpty') {
            if (!value) {
                return false;
            }

            let nameValue = this.$el.find('[data-name="' + this.nameName + '"]').val();

            return {
                type: 'notEquals',
                attribute: this.idName,
                value: value,
                data: {
                    type: type,
                    idValue: value,
                    nameValue: nameValue,
                },
            };
        }

        if (!value) {
            return false;
        }

        let nameValue = this.$el.find('[data-name="' + this.nameName + '"]').val();

        return {
            type: 'equals',
            attribute: this.idName,
            value: value,
            data: {
                type: type,
                idValue: value,
                nameValue: nameValue,
            }
        };
    }

    /** @inheritDoc */
    getSearchType() {
        return this.getSearchParamsData().type ||
            this.searchParams.typeFront ||
            this.searchParams.type;
    }

    /**
     * @protected
     */
    quickView() {
        let id = this.model.get(this.idName);

        if (!id) {
            return;
        }

        let entityType = this.foreignScope;

        let helper = new RecordModal(this.getMetadata(), this.getAcl());

        helper.showDetail(this, {
            id: id,
            scope: entityType,
        });
    }

    /**
     * @return {function(): Promise<Object.<string, *>>}
     */
    getCreateAttributesProvider() {
        return () => {
            const attributes = this.getCreateAttributes() || {};

            if (!this.panelDefs.createHandler) {
                return Promise.resolve(attributes);
            }

            return new Promise(resolve => {
                Espo.loader.requirePromise(this.panelDefs.createHandler)
                    .then(Handler => new Handler(this.getHelper()))
                    .then(handler => {
                        handler.getAttributes(this.model)
                            .then(additionalAttributes => {
                                resolve({
                                    ...attributes,
                                    ...additionalAttributes,
                                });
                            });
                    });
            });
        };
    }

    /**
     * @protected
     */
    actionSelect() {
        Espo.Ui.notify(' ... ');

        const panelDefs = this.panelDefs;

        const viewName = panelDefs.selectModalView ||
            this.getMetadata().get(['clientDefs', this.foreignScope, 'modalViews', 'select']) ||
            this.selectRecordsView;

        const mandatorySelectAttributeList = this.mandatorySelectAttributeList ||
            panelDefs.selectMandatoryAttributeList;

        const createButton = this.isEditMode() && (!this.createDisabled || this.forceCreateButton);

        const createAttributesProvider = createButton ?
            this.getCreateAttributesProvider() :
            null;

        this._getSelectFilters().then(filters => {
            this.createView('dialog', viewName, {
                scope: this.foreignScope,
                createButton: createButton,
                filters: filters.advanced,
                boolFilterList: filters.bool,
                primaryFilterName: filters.primary,
                mandatorySelectAttributeList: mandatorySelectAttributeList,
                forceSelectAllAttributes: this.forceSelectAllAttributes,
                filterList: this.getSelectFilterList(),
                createAttributesProvider: createAttributesProvider,
                layoutName: this.panelDefs.selectLayout,
            }, view => {
                view.render();

                Espo.Ui.notify(false);

                this.listenToOnce(view, 'select', model => {
                    this.clearView('dialog');

                    this.select(model);
                });
            });
        });
    }

    /**
     * @private
     * @return {Promise<{bool?: string[], advanced?: Object, primary?: string}>}
     */
    _getSelectFilters() {
        const handler = this.panelDefs.selectHandler;

        const localBoolFilterList = this.getSelectBoolFilterList();

        if (!handler || this.isSearchMode()) {
            const boolFilterList = (localBoolFilterList || this.panelDefs.selectBoolFilterList) ?
                [
                    ...(localBoolFilterList || []),
                    ...(this.panelDefs.selectBoolFilterList || []),
                ] :
                undefined;

            return Promise.resolve({
                primary: this.getSelectPrimaryFilterName() || this.panelDefs.selectPrimaryFilterName,
                bool: boolFilterList,
                advanced: this.getSelectFilters() || undefined,
            });
        }

        return new Promise(resolve => {
            Espo.loader.requirePromise(handler)
                .then(Handler => new Handler(this.getHelper()))
                .then(/** module:handlers/select-related */handler => {
                    return handler.getFilters(this.model);
                })
                .then(filters => {
                    const advanced = {...(this.getSelectFilters() || {}), ...(filters.advanced || {})};
                    const primaryFilter = this.getSelectPrimaryFilterName() ||
                        filters.primary || this.panelDefs.selectPrimaryFilterName;

                    const boolFilterList = (localBoolFilterList || filters.bool || this.panelDefs.selectBoolFilterList) ?
                        [
                            ...(localBoolFilterList || []),
                            ...(filters.bool || []),
                            ...(this.panelDefs.selectBoolFilterList || []),
                        ] :
                        undefined;

                    resolve({
                        bool: boolFilterList,
                        primary: primaryFilter,
                        advanced: advanced,
                    });
                });
        });
    }

    actionSelectOneOf() {
        Espo.Ui.notify(' ... ');

        let viewName = this.getMetadata()
                .get(['clientDefs', this.foreignScope, 'modalViews', 'select']) ||
            this.selectRecordsView;

        this.createView('dialog', viewName, {
            scope: this.foreignScope,
            createButton: false,
            filters: this.getSelectFilters(),
            boolFilterList: this.getSelectBoolFilterList(),
            primaryFilterName: this.getSelectPrimaryFilterName(),
            multiple: true,
            layoutName: this.panelDefs.selectLayout,
        }, view => {
            view.render();

            Espo.Ui.notify(false);

            this.listenToOnce(view, 'select', models => {
                this.clearView('dialog');

                if (Object.prototype.toString.call(models) !== '[object Array]') {
                    models = [models];
                }

                this.selectOneOf(models);
            });
        });
    }

    getEmptyAutocompleteResult() {
        return undefined;
    }

    actionCreateLink() {
        const viewName = this.getMetadata().get(['clientDefs', this.foreignScope, 'modalViews', 'edit']) ||
            'views/modals/edit';

        Espo.Ui.notify(' ... ');

        this.getCreateAttributesProvider()().then(attributes => {
            this.createView('dialog', viewName, {
                scope: this.foreignScope,
                fullFormDisabled: true,
                attributes: attributes,
            }, view => {
                view.render()
                    .then(() => Espo.Ui.notify(false));

                this.listenToOnce(view, 'after:save', model => {
                    view.close();
                    this.clearView('dialog');

                    this.select(model);
                });
            });
        });
    }

    /**
     * @protected
     * @param {module:model[]} models
     * @since 8.0.4
     */
    selectOneOf(models) {
        models.forEach(model => {
            this.addLinkOneOf(model.id, model.get('name'));
        });
    }
}

export default LinkFieldView;
PK]n��܏�views/fields/foreign-int.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import IntFieldView from 'views/fields/int';
import Helper from 'helpers/misc/foreign-field';

class ForeignIntFieldView extends IntFieldView {

    type = 'foreign'

    setup() {
        super.setup();

        const helper = new Helper(this);

        const foreignParams = helper.getForeignParams();

        for (let param in foreignParams) {
            this.params[param] = foreignParams[param];
        }

        this.disableFormatting = foreignParams.disableFormatting;
    }
}

export default ForeignIntFieldView;

PK]��,�a�aviews/fields/file.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/fields/file */

import LinkFieldView from 'views/fields/link';
import FileUpload from 'helpers/file-upload';

/**
 * A file field.
 */
class FileFieldView extends LinkFieldView {

    type = 'file'

    listTemplate = 'fields/file/list'
    listLinkTemplate = 'fields/file/list'
    detailTemplate = 'fields/file/detail'
    editTemplate = 'fields/file/edit'

    showPreview = false
    accept = false
    defaultType = false
    previewSize = 'small'
    validations = ['ready', 'required']
    searchTypeList = ['isNotEmpty', 'isEmpty']

    ROW_HEIGHT = 37

    events = {
        /** @this FileFieldView */
        'click a.remove-attachment': function (e) {
            let $div = $(e.currentTarget).parent();

            this.deleteAttachment();

            $div.parent().remove();

            this.$el.find('input.file').val(null);

            setTimeout(() => this.focusOnUploadButton(), 10);
        },
        /** @this FileFieldView */
        'change input.file': function (e) {
            let $file = $(e.currentTarget);
            let files = e.currentTarget.files;

            if (!files.length) {
                return;
            }

            this.uploadFile(files[0]);

            e.target.value = null;

            $file.replaceWith($file.clone(true));
        },
        /** @this FileFieldView */
        'click a[data-action="showImagePreview"]': function (e) {
            e.preventDefault();

            let id = this.model.get(this.idName);

            this.createView('preview', 'views/modals/image-preview', {
                id: id,
                model: this.model,
                name: this.model.get(this.nameName),
            }, view => {
                view.render();
            });
        },
        /** @this FileFieldView */
        'click a.action[data-action="insertFromSource"]': function (e) {
            let name = $(e.currentTarget).data('name');

            this.insertFromSource(name);
        },
        /** @this FileFieldView */
        'keydown label.attach-file-label': function (e) {
            let key = Espo.Utils.getKeyFromKeyEvent(e);

            if (key === 'Enter') {
                this.$el.find('input.file').get(0).click();
            }
        },
    }

    data() {
        let data =  {
            ...super.data(),
            id: this.model.get(this.idName),
            acceptAttribute: this.acceptAttribute,
        };

        if (this.mode === this.MODE_EDIT) {
            data.sourceList = this.sourceList;
        }

        data.valueIsSet = this.model.has(this.idName);

        return data;
    }

    showValidationMessage(msg, selector) {
        let $label = this.$el.find('label');

        let title = $label.attr('title');

        $label.attr('title', '');

        super.showValidationMessage(msg, selector);

        $label.attr('title', title);
    }

    validateRequired() {
        if (!this.isRequired()) {
            return;
        }

        if (this.model.get(this.idName) == null) {
            let msg = this.translate('fieldIsRequired', 'messages')
                .replace('{field}', this.getLabelText());

            let $target;

            if (this.isUploading) {
                $target = this.$el.find('.gray-box');
            } else {
                $target = this.$el.find('.attachment-button label');
            }

            this.showValidationMessage(msg, $target);

            return true;
        }
    }

    validateReady() {
        if (this.isUploading) {
            let $target = this.$el.find('.gray-box');

            let msg = this.translate('fieldIsUploading', 'messages')
                .replace('{field}', this.getLabelText());

            this.showValidationMessage(msg, $target);

            return true;
        }
    }

    setup() {
        this.nameName = this.name + 'Name';
        this.idName = this.name + 'Id';
        this.typeName = this.name + 'Type';
        this.foreignScope = 'Attachment';

        this.previewSize = this.options.previewSize || this.params.previewSize || this.previewSize;

        this.previewTypeList = this.getMetadata().get(['app', 'image', 'previewFileTypeList']) || [];
        this.imageSizes = this.getMetadata().get(['app', 'image', 'sizes']) || {};

        let sourceDefs = this.getMetadata().get(['clientDefs', 'Attachment', 'sourceDefs']) || {};

        this.sourceList = Espo.Utils.clone(this.params.sourceList || []);

        this.sourceList = this.sourceList
            .concat(
                this.getMetadata().get(['clientDefs', 'Attachment', 'generalSourceList']) || []
            )
            .filter((item, i, self) => {
                return self.indexOf(item) === i;
            })
            .filter(item => {
                let defs = sourceDefs[item] || {};

                if (defs.accessDataList) {
                    if (
                        !Espo.Utils.checkAccessDataList(
                            defs.accessDataList, this.getAcl(), this.getUser()
                        )
                    ) {
                        return false;
                    }
                }

                if (defs.configCheck) {
                    let arr = defs.configCheck.split('.');

                    if (!this.getConfig().getByPath(arr)) {
                        return false;
                    }
                }

                return true;
            });

        if ('showPreview' in this.params) {
            this.showPreview = this.params.showPreview;
        }

        if ('accept' in this.params) {
            this.accept = this.params.accept;
        }

        if (this.accept && this.accept.length) {
            this.acceptAttribute = this.accept.join(', ');
        }

        this.on('remove', () => {
            if (this.resizeIsBeingListened) {
                $(window).off('resize.' + this.cid);
            }
        });

        this.on('inline-edit-off', () => {
            this.isUploading = false;
        });
    }

    afterRender() {
        if (this.mode === this.MODE_EDIT) {
            this.$attachment = this.$el.find('div.attachment');

            let name = this.model.get(this.nameName);
            let type = this.model.get(this.typeName) || this.defaultType;
            let id = this.model.get(this.idName);

            if (id) {
                this.addAttachmentBox(name, type, id);
            }

            this.$el.off('drop');
            this.$el.off('dragover');
            this.$el.off('dragleave');

            this.$el.on('drop', e => {
                e.preventDefault();
                e.stopPropagation();

                event = e.originalEvent;

                if (
                    event.dataTransfer &&
                    event.dataTransfer.files &&
                    event.dataTransfer.files.length
                ) {
                    this.uploadFile(event.dataTransfer.files[0]);
                }
            });

            this.$el.on('dragover', e => {
                e.preventDefault();
            });

            this.$el.on('dragleave', e =>{
                e.preventDefault();
            });
        }

        if (this.mode === this.MODE_SEARCH) {
            let type = this.$el.find('select.search-type').val();

            this.handleSearchType(type);
        }

        if (this.mode === this.MODE_DETAIL) {
            if (this.previewSize === 'large') {
                this.handleResize();
                this.resizeIsBeingListened = true;

                $(window).on('resize.' + this.cid, () => {
                    this.handleResize();
                });
            }
        }
    }

    focusOnInlineEdit() {
        this.focusOnUploadButton();
    }

    focusOnUploadButton() {
        let $element = this.$el.find('.attach-file-label');

        if ($element.length) {
            $element.focus();
        }
    }

    handleResize() {
        let width = this.$el.width();

        this.$el.find('img.image-preview').css('maxWidth', width + 'px');
    }

    /**
     * @return {string}
     */
    getDetailPreview(name, type, id) {
        if (!~this.previewTypeList.indexOf(type)) {
            return name;
        }

        let previewSize = this.previewSize;

        if (this.isListMode()) {
            previewSize = this.params.listPreviewSize || 'small';
        }

        let src = this.getBasePath() + '?entryPoint=image&size=' + previewSize + '&id=' + id;

        let maxHeight = (this.imageSizes[previewSize] || {})[1];

        if (this.isListMode() && !this.params.listPreviewSize) {
            maxHeight = this.ROW_HEIGHT + 'px';
        }

        let $img = $('<img>')
            .attr('src', src)
            .addClass('image-preview')
            .css({
                maxWidth: (this.imageSizes[previewSize] || {})[0],
                maxHeight: maxHeight,
            });

        if (this.mode === this.MODE_LIST_LINK) {
            let link = '#' + this.model.entityType + '/view/' + this.model.id;

            return $('<a>')
                .attr('href', link)
                .append($img)
                .get(0)
                .outerHTML;
        }

        return $('<a>')
            .attr('data-action', 'showImagePreview')
            .attr('data-id', id)
            .attr('title', name)
            .attr('href', this.getImageUrl(id))
            .append($img)
            .get(0)
            .outerHTML;
    }

    getEditPreview(name, type, id) {
        if (!~this.previewTypeList.indexOf(type)) {
            return null;
        }

        return $('<img>')
            .attr('src', this.getImageUrl(id, 'small'))
            .attr('title', name)
            .attr('draggable', false)
            .css({
                maxWidth: (this.imageSizes[this.previewSize] || {})[0],
                maxHeight: (this.imageSizes[this.previewSize] || {})[1],
            })
            .get(0)
            .outerHTML;
    }

    getValueForDisplay() {
        if (! (this.isDetailMode() || this.isListMode())) {
            return '';
        }

        let name = this.model.get(this.nameName);
        let type = this.model.get(this.typeName) || this.defaultType;
        let id = this.model.get(this.idName);

        if (!id) {
            return false;
        }

        if (this.showPreview && ~this.previewTypeList.indexOf(type)) {
            let className = '';

            if (this.isListMode() && this.params.listPreviewSize) {
                className += 'no-shrink';
            }

            let $item = $('<div>')
                .addClass('attachment-preview')
                .addClass(className)
                .append(
                    this.getDetailPreview(name, type, id)
                );

            let containerClassName = 'attachment-block-container';

            if (this.previewSize === 'large') {
                containerClassName += ' attachment-block-container-large';
            }

            if (this.previewSize === 'small') {
                containerClassName += ' attachment-block-container-small';
            }

            return $('<div>')
                .addClass(containerClassName)
                .append(
                    $('<div>')
                        .addClass('attachment-block')
                        .append($item)
                )
                .get(0).outerHTML;
        }

        return $('<span>')
            .append(
                $('<span>').addClass('fas fa-paperclip text-soft small'),
                ' ',
                $('<a>')
                    .attr('href', this.getDownloadUrl(id))
                    .attr('target', '_BLANK')
                    .text(name)
            )
            .get(0).innerHTML;
    }

    getImageUrl(id, size) {
        let url = this.getBasePath() + '?entryPoint=image&id=' + id;

        if (size) {
            url += '&size=' + size;
        }

        if (this.getUser().get('portalId')) {
            url += '&portalId=' + this.getUser().get('portalId');
        }

        return url;
    }

    getDownloadUrl(id) {
        let url = this.getBasePath() + '?entryPoint=download&id=' + id;

        if (this.getUser().get('portalId')) {
            url += '&portalId=' + this.getUser().get('portalId');
        }

        return url;
    }

    deleteAttachment() {
        let id = this.model.get(this.idName);

        let o = {};

        o[this.idName] = null;
        o[this.nameName] = null;

        this.model.set(o);

        this.$attachment.empty();

        if (id) {
            if (this.model.isNew()) {
                this.getModelFactory().create('Attachment', (attachment) => {
                    attachment.id = id;
                    attachment.destroy();
                });
            }
        }
    }

    setAttachment(attachment, ui) {
        let o = {};

        o[this.idName] = attachment.id;
        o[this.nameName] = attachment.get('name');

        this.model.set(o, {ui: ui});
    }

    getMaxFileSize() {
        let maxFileSize = this.params.maxFileSize || 0;

        let noChunk = !this.getConfig().get('attachmentUploadChunkSize');
        let attachmentUploadMaxSize = this.getConfig().get('attachmentUploadMaxSize') || 0;
        let appMaxUploadSize = this.getHelper().getAppParam('maxUploadSize') || 0;

        if (!maxFileSize || maxFileSize > attachmentUploadMaxSize) {
            maxFileSize = attachmentUploadMaxSize;
        }

        if (noChunk && maxFileSize > appMaxUploadSize) {
            maxFileSize = appMaxUploadSize;
        }

        return maxFileSize;
    }

    uploadFile(file) {
        let isCanceled = false;

        let exceedsMaxFileSize = false;

        let maxFileSize = this.getMaxFileSize();

        if (maxFileSize) {
            if (file.size > maxFileSize * 1024 * 1024) {
                exceedsMaxFileSize = true;
            }
        }

        if (exceedsMaxFileSize) {
            let msg = this.translate('fieldMaxFileSizeError', 'messages')
                .replace('{field}', this.getLabelText())
                .replace('{max}', maxFileSize);

            this.showValidationMessage(msg, '.attachment-button label');

            return;
        }

        this.isUploading = true;

        let uploadHelper = new FileUpload(this.getConfig());

        this.getModelFactory().create('Attachment', attachment => {
            let $attachmentBox = this.addAttachmentBox(file.name, file.type);

            let $uploadingMsg = $attachmentBox.parent().find('.uploading-message');

            this.$el.find('.attachment-button').addClass('hidden');

            let mediator = {};

            $attachmentBox.find('.remove-attachment').on('click.uploading', () => {
                isCanceled = true;
                this.isUploading = false;

                this.$el.find('.attachment-button').removeClass('hidden');
                this.$el.find('input.file').val(null);

                mediator.isCanceled = true;
            });

            attachment.set('role', 'Attachment');
            attachment.set('relatedType', this.model.entityType);
            attachment.set('field', this.name);

            this.handleUploadingFile(file).then(file => {
                uploadHelper
                    .upload(file, attachment, {
                        afterChunkUpload: (size) => {
                            let msg = Math.floor((size / file.size) * 100) + '%';

                            $uploadingMsg.html(msg);
                        },
                        afterAttachmentSave: (attachment) => {
                            $attachmentBox.attr('data-id', attachment.id);
                        },
                        mediator: mediator,
                    })
                    .then(() => {
                        if (isCanceled) {
                            return;
                        }

                        if (!this.isUploading) {
                            return;
                        }

                        this.setAttachment(attachment, true);

                        $attachmentBox.trigger('ready');

                        this.isUploading = false;

                        setTimeout(() => {
                            if (
                                document.activeElement &&
                                document.activeElement.tagName !== 'BODY'
                            ) {
                                return;
                            }

                            let $a = this.$el.find('.preview a');
                            $a.focus();
                        }, 50);
                    })
                    .catch(() => {
                        if (mediator.isCanceled) {
                            return;
                        }

                        $attachmentBox.remove();

                        this.$el.find('.uploading-message').remove();
                        this.$el.find('.attachment-button').removeClass('hidden');

                        this.isUploading = false;
                    });
            });
        });
    }

    handleUploadingFile(file) {
        return new Promise(resolve => resolve(file));
    }

    getBoxPreviewHtml(name, type, id) {
        let $text = $('<span>').text(name);

        if (!id) {
            return $text.get(0).outerHTML;
        }

        if (this.showPreview) {
            let html = this.getEditPreview(name, type, id);

            if (html) {
                return html;
            }
        }

        let url = this.getBasePath() + '?entryPoint=download&id=' + id;

        return $('<a>')
            .attr('href', url)
            .attr('target', '_BLANK')
            .text(name)
            .get(0).outerHTML;
    }

    addAttachmentBox(name, type, id) {
        this.$attachment.empty();

        let $remove = $('<a>')
            .attr('role', 'button')
            .attr('tabindex', '0')
            .addClass('remove-attachment pull-right')
            .append(
                $('<span>').addClass('fas fa-times')
            );

        let previewHtml = this.getBoxPreviewHtml(name, type, id);

        let $att = $('<div>')
            .addClass('gray-box')
            .append($remove)
            .append(
                $('<span>')
                    .addClass('preview')
                    .append(previewHtml)
            );

        let $container = $('<div>').append($att);

        this.$attachment.append($container);

        if (id) {
            return $att;
        }

        let $loading = $('<span>')
            .addClass('small uploading-message')
            .text(this.translate('Uploading...'));

        $container.append($loading);

        $att.on('ready', () => {
            let id = this.model.get(this.idName);

            let previewHtml = this.getBoxPreviewHtml(name, type, id);

            $att.find('.preview').html(previewHtml);

            $loading.html(this.translate('Ready'));

            if ($att.find('.preview').find('img').length) {
                $loading.remove();
            }
        });

        return $att;
    }

    insertFromSource(source) {
        let viewName =
            this.getMetadata()
                .get(['clientDefs', 'Attachment', 'sourceDefs', source, 'insertModalView']) ||
            this.getMetadata().get(['clientDefs', source, 'modalViews', 'select']) ||
            'views/modals/select-records';

        if (viewName) {
            Espo.Ui.notify(' ... ');

            let filters = null;

            if (('getSelectFilters' + source) in this) {
                filters = this['getSelectFilters' + source]();

                if (this.model.get('parentId') && this.model.get('parentType') === 'Account') {
                    if (
                        this.getMetadata()
                            .get(['entityDefs', source, 'fields', 'account', 'type']) === 'link'
                    ) {
                        filters = {
                            account: {
                                type: 'equals',
                                field: 'accountId',
                                value: this.model.get('parentId'),
                                valueName: this.model.get('parentName'),
                            }
                        };
                    }
                }
            }

            let boolFilterList = this.getMetadata().get(
                ['clientDefs', 'Attachment', 'sourceDefs', source, 'boolFilterList']
            );

            if (('getSelectBoolFilterList' + source) in this) {
                boolFilterList = this['getSelectBoolFilterList' + source]();
            }

            let primaryFilterName = this.getMetadata().get(
                ['clientDefs', 'Attachment', 'sourceDefs', source, 'primaryFilter']
            );

            if (('getSelectPrimaryFilterName' + source) in this) {
                primaryFilterName = this['getSelectPrimaryFilterName' + source]();
            }

            this.createView('insertFromSource', viewName, {
                scope: source,
                createButton: false,
                filters: filters,
                boolFilterList: boolFilterList,
                primaryFilterName: primaryFilterName,
                multiple: false,
            }, (view) => {
                view.render();

                Espo.Ui.notify(false);

                this.listenToOnce(view, 'select', (modelList) => {
                    if (Object.prototype.toString.call(modelList) !== '[object Array]') {
                        modelList = [modelList];
                    }

                    modelList.forEach(model => {
                        if (model.entityType === 'Attachment') {
                            this.setAttachment(model);

                            return;
                        }

                        Espo.Ajax
                            .postRequest(source + '/action/getAttachmentList', {
                                id: model.id,
                                field: this.name,
                                relatedType: this.entityType,
                            })
                            .then(attachmentList => {
                                attachmentList.forEach(item => {
                                    this.getModelFactory().create('Attachment', (attachment) => {
                                        attachment.set(item);

                                        this.setAttachment(attachment);
                                    });
                                });
                            });
                    });
                });
            });
        }
    }

    fetch() {
        let data = {};

        data[this.idName] = this.model.get(this.idName);

        return data;
    }
}

export default FileFieldView;
PK]1�b�,�,views/fields/text.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/fields/text */

import BaseFieldView from 'views/fields/base';

/**
 * A text field.
 */
class TextFieldView extends BaseFieldView {

    type = 'text'

    listTemplate = 'fields/text/list'
    detailTemplate = 'fields/text/detail'
    editTemplate = 'fields/text/edit'
    searchTemplate = 'fields/text/search'

    seeMoreText = false
    rowsDefault = 10
    rowsMin = 2
    seeMoreDisabled = false
    cutHeight = 200
    noResize = false
    changeInterval = 5

    searchTypeList = [
        'contains',
        'startsWith',
        'equals',
        'endsWith',
        'like',
        'notContains',
        'notLike',
        'isEmpty',
        'isNotEmpty',
    ]

    events = {
        /** @this TextFieldView */
        'click a[data-action="seeMoreText"]': function () {
            this.seeMoreText = true;

            this.reRender();
        },
        /** @this TextFieldView */
        'click [data-action="mailTo"]': function (e) {
            this.mailTo($(e.currentTarget).data('email-address'));
        },
    }

    setup() {
        super.setup();

        this.params.rows = this.params.rows || this.rowsDefault;
        this.noResize = this.options.noResize || this.params.noResize || this.noResize;
        this.seeMoreDisabled = this.seeMoreDisabled || this.params.seeMoreDisabled;
        this.autoHeightDisabled = this.options.autoHeightDisabled || this.params.autoHeightDisabled ||
            this.autoHeightDisabled;

        if (this.params.cutHeight) {
            this.cutHeight = this.params.cutHeight;
        }

        this.rowsMin = this.options.rowsMin || this.params.rowsMin || this.rowsMin;

        if (this.params.rows < this.rowsMin) {
            this.rowsMin = this.params.rows;
        }

        this.on('remove', () => {
            $(window).off('resize.see-more-' + this.cid);
        });
    }

    setupSearch() {
        this.events['change select.search-type'] = e => {
            let type = $(e.currentTarget).val();

            this.handleSearchType(type);
        };
    }

    data() {
        let data = super.data();

        if (
            this.model.get(this.name) !== null &&
            this.model.get(this.name) !== '' &&
            this.model.has(this.name)
        ) {
            data.isNotEmpty = true;
        }

        if (this.mode === this.MODE_SEARCH) {
            if (typeof this.searchParams.value === 'string') {
                this.searchData.value = this.searchParams.value;
            }
        }

        if (this.mode === this.MODE_EDIT) {
            if (this.autoHeightDisabled) {
                data.rows = this.params.rows;
            } else {
                data.rows = this.rowsMin;
            }
        }

        data.valueIsSet = this.model.has(this.name);

        if (this.isReadMode()) {
            data.isCut = this.isCut();

            if (data.isCut) {
                data.cutHeight = this.cutHeight;
            }

            data.displayRawText = this.params.displayRawText;
        }

        data.noResize = this.noResize;

        return data;
    }

    handleSearchType(type) {
        if (~['isEmpty', 'isNotEmpty'].indexOf(type)) {
            this.$el.find('input.main-element').addClass('hidden');
        } else {
            this.$el.find('input.main-element').removeClass('hidden');
        }
    }

    getValueForDisplay() {
        let text = this.model.get(this.name);

        return text || '';
    }

    controlTextareaHeight(lastHeight) {
        var scrollHeight = this.$element.prop('scrollHeight');
        var clientHeight = this.$element.prop('clientHeight');

        if (typeof lastHeight === 'undefined' && clientHeight === 0) {
            setTimeout(this.controlTextareaHeight.bind(this), 10);

            return;
        }

        if (clientHeight === lastHeight) {
            return;
        }

        if (scrollHeight > clientHeight + 1) {
            var rows = this.$element.prop('rows');

            if (this.params.rows && rows >= this.params.rows) {
                return;
            }

            this.$element.attr('rows', rows + 1);
            this.controlTextareaHeight(clientHeight);
        }

        if (this.$element.val().length === 0) {
            this.$element.attr('rows', this.rowsMin);
        }
    }

    isCut() {
        return !this.seeMoreText && !this.seeMoreDisabled;
    }

    controlSeeMore() {
        if (!this.isCut()) {
            return;
        }

        if (this.$text.height() > this.cutHeight) {
            this.$seeMoreContainer.removeClass('hidden');
            this.$textContainer.addClass('cut');
        } else {
            this.$seeMoreContainer.addClass('hidden');
            this.$textContainer.removeClass('cut');
        }
    }

    afterRender() {
        super.afterRender();

        if (this.isReadMode()) {
            $(window).off('resize.see-more-' + this.cid);

            this.$textContainer = this.$el.find('> .complex-text-container');
            this.$text = this.$textContainer.find('> .complex-text');
            this.$seeMoreContainer = this.$el.find('> .see-more-container');

            if (this.isCut()) {
                this.controlSeeMore();

                if (this.model.get(this.name) && this.$text.height() === 0) {
                    this.$textContainer.addClass('cut');

                    setTimeout(this.controlSeeMore.bind(this), 50);
                }

                this.listenTo(this.recordHelper, 'panel-show', () => this.controlSeeMore());
                this.on('panel-show-propagated', () => this.controlSeeMore());

                $(window).on('resize.see-more-' + this.cid, () => {
                    this.controlSeeMore();
                });
            }
        }

        if (this.mode === this.MODE_EDIT) {
            var text = this.getValueForDisplay();
            if (text) {
                this.$element.val(text);
            }
        }

        if (this.mode === this.MODE_SEARCH) {
            var type = this.$el.find('select.search-type').val();

            this.handleSearchType(type);

            this.$el.find('select.search-type').on('change', () => {
                this.trigger('change');
            });

            this.$element.on('input', () => {
                this.trigger('change');
            });
        }

        if (this.mode === this.MODE_EDIT && !this.autoHeightDisabled) {
            this.controlTextareaHeight();

            this.$element.on('input', () => {
                this.controlTextareaHeight();
            });

            let lastChangeKeydown = new Date();
            const changeKeydownInterval = this.changeInterval * 1000;

            this.$element.on('keydown', () => {
                if (Date.now() - lastChangeKeydown > changeKeydownInterval) {
                    this.trigger('change');
                    lastChangeKeydown = Date.now();
                }
            });
        }
    }

    fetch() {
        let data = {};

        let value = this.$element.val() || null;

        if (value && value.trim() === '') {
            value = '';
        }

        data[this.name] = value

        return data;
    }

    fetchSearch() {
        let type = this.fetchSearchType() || 'startsWith';

        if (type === 'isEmpty') {
            return  {
                type: 'or',
                value: [
                    {
                        type: 'isNull',
                        field: this.name,
                    },
                    {
                        type: 'equals',
                        field: this.name,
                        value: ''
                    }
                ],
                data: {
                    type: type,
                },
            };
        }

        if (type === 'isNotEmpty') {
            return  {
                type: 'and',
                value: [
                    {
                        type: 'notEquals',
                        field: this.name,
                        value: '',
                    },
                    {
                        type: 'isNotNull',
                        field: this.name,
                        value: null,
                    }
                ],
                data: {
                    type: type,
                },
            };
        }

        let value = this.$element.val().toString().trim();

        if (value) {
            return {
                value: value,
                type: type,
            };
        }

        return false;
    }

    getSearchType() {
        return this.getSearchParamsData().type || this.searchParams.typeFront ||
            this.searchParams.type;
    }

    mailTo(emailAddress) {
        let attributes = {
            status: 'Draft',
            to: emailAddress,
        };

        if (
            this.getConfig().get('emailForceUseExternalClient') ||
            this.getPreferences().get('emailUseExternalClient') ||
            !this.getAcl().checkScope('Email', 'create')
        ) {
            Espo.loader.require('email-helper', EmailHelper => {
                let emailHelper = new EmailHelper();

                document.location.href = emailHelper
                    .composeMailToLink(attributes, this.getConfig().get('outboundEmailBccAddress'));
            });

            return;
        }

        let viewName = this.getMetadata().get('clientDefs.' + this.scope + '.modalViews.compose') ||
            'views/modals/compose-email';

        Espo.Ui.notify(' ... ');

        this.createView('quickCreate', viewName, {
            attributes: attributes,
        }, view => {
            view.render();
            view.notify(false);
        });
    }
}

export default TextFieldView;
PK]hxRcQ
Q
views/fields/colorpicker.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import VarcharFieldView from 'views/fields/varchar';

class ColorpickerFieldView extends VarcharFieldView {

    type = 'varchar'

    detailTemplate = 'fields/colorpicker/detail'
    listTemplate = 'fields/colorpicker/detail'
    editTemplate = 'fields/colorpicker/edit'

    setup() {
        super.setup();

        this.wait(Espo.loader.requirePromise('lib!bootstrap-colorpicker'));
    }

    afterRender() {
        super.afterRender();

        if (this.isEditMode()) {
            let isModal = !!this.$el.closest('.modal').length;

            // noinspection JSUnresolvedReference
            this.$element.parent().colorpicker({
                format: 'hex',
                container: isModal ? this.$el : false,
            });

            if (isModal) {
                this.$el.find('.colorpicker')
                    .css('position', 'relative')
                    .addClass('pull-right');
            }

            this.$element.on('change', () => {
                if (this.$element.val() === '') {
                    this.$el.find('.input-group-addon > i').css('background-color', 'transparent');
                }
            });
        }
    }
}

export default ColorpickerFieldView;
PK]�zzn#n#views/fields/datetime.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/fields/datetime */

import DateFieldView from 'views/fields/date';
import moment from 'moment';

/**
 * A date-time field.
 */
class DatetimeFieldView extends DateFieldView {

    type = 'datetime'

    editTemplate = 'fields/datetime/edit'

    validations = ['required', 'datetime', 'after', 'before']

    searchTypeList = [
        'lastSevenDays',
        'ever',
        'isEmpty',
        'currentMonth',
        'lastMonth',
        'nextMonth',
        'currentQuarter',
        'lastQuarter',
        'currentYear',
        'lastYear',
        'today',
        'past',
        'future',
        'lastXDays',
        'nextXDays',
        'olderThanXDays',
        'afterXDays',
        'on',
        'after',
        'before',
        'between',
    ]

    timeFormatMap = {
        'HH:mm': 'H:i',
        'hh:mm A': 'h:i A',
        'hh:mm a': 'h:i a',
        'hh:mmA': 'h:iA',
        'hh:mma': 'h:ia',
    }

    data() {
        let data = super.data();

        data.date = data.time = '';

        let value = this.getDateTime().toDisplay(this.model.get(this.name));

        if (value) {
            let pair = this.splitDatetime(value);

            data.date = pair[0];
            data.time = pair[1];
        }

        return data;
    }

    getDateStringValue() {
        if (this.mode === this.MODE_DETAIL && !this.model.has(this.name)) {
            return -1;
        }

        let value = this.model.get(this.name);

        if (!value) {
            if (
                this.mode === this.MODE_EDIT |
                this.mode === this.MODE_SEARCH |
                this.mode === this.MODE_LIST ||
                this.mode === this.MODE_LIST_LINK
            ) {
                return '';
            }

            return null;
        }

        if (
            this.mode === this.MODE_LIST ||
            this.mode === this.MODE_DETAIL ||
            this.mode === this.MODE_LIST_LINK
        ) {
            if (this.getConfig().get('readableDateFormatDisabled') || this.params.useNumericFormat) {
                return this.getDateTime().toDisplay(value);
            }

            let timeFormat = this.getDateTime().timeFormat;

            if (this.params.hasSeconds) {
                timeFormat = timeFormat.replace(/:mm/, ':mm:ss');
            }

            let d = this.getDateTime().toMoment(value);
            let now = moment().tz(this.getDateTime().timeZone || 'UTC');
            let dt = now.clone().startOf('day');

            let ranges = {
                'today': [dt.unix(), dt.add(1, 'days').unix()],
                'tomorrow': [dt.unix(), dt.add(1, 'days').unix()],
                'yesterday': [dt.add(-3, 'days').unix(), dt.add(1, 'days').unix()]
            };

            if (d.unix() >= ranges['today'][0] && d.unix() < ranges['today'][1]) {
                return this.translate('Today') + ' ' + d.format(timeFormat);
            }
            else if (d.unix() > ranges['tomorrow'][0] && d.unix() < ranges['tomorrow'][1]) {
                return this.translate('Tomorrow') + ' ' + d.format(timeFormat);
            }
            else if (d.unix() > ranges['yesterday'][0] && d.unix() < ranges['yesterday'][1]) {
                return this.translate('Yesterday') + ' ' + d.format(timeFormat);
            }

            let readableFormat = this.getDateTime().getReadableDateFormat();

            if (d.format('YYYY') === now.format('YYYY')) {
                return d.format(readableFormat) + ' ' + d.format(timeFormat);
            }
            else {
                return d.format(readableFormat + ', YYYY') + ' ' + d.format(timeFormat);
            }
        }

        return this.getDateTime().toDisplay(value);
    }

    initTimepicker() {
        let $time = this.$time;

        $time.timepicker({
            step: this.params.minuteStep || 30,
            scrollDefaultNow: true,
            timeFormat: this.timeFormatMap[this.getDateTime().timeFormat],
        });

        $time
            .parent()
            .find('button.time-picker-btn')
            .on('click', () => {
                $time.timepicker('show');
            });
    }

    setDefaultTime() {
        let dtString = moment('2014-01-01 00:00').format(this.getDateTime().getDateTimeFormat()) || '';

        let pair = this.splitDatetime(dtString);

        if (pair.length === 2) {
            this.$time.val(pair[1]);
        }
    }

    splitDatetime(value) {
        let m = moment(value, this.getDateTime().getDateTimeFormat());

        let dateValue = m.format(this.getDateTime().getDateFormat());
        let timeValue = value.substr(dateValue.length + 1);

        return [dateValue, timeValue];
    }

    setup() {
        super.setup();

        this.on('remove', () => this.destroyTimepicker());
        this.on('mode-changed', () => this.destroyTimepicker());
    }

    destroyTimepicker() {
        if (this.$time && this.$time[0]) {
            this.$time.timepicker('remove');
        }
    }

    afterRender() {
        super.afterRender();

        if (this.mode !== this.MODE_EDIT) {
            return;
        }

        this.$date = this.$element;
        let $time = this.$time = this.$el.find('input.time-part');

        this.initTimepicker();

        this.$element.on('change.datetime', () => {
            if (this.$element.val() && !$time.val()) {
                this.setDefaultTime();
                this.trigger('change');
            }
        });

        let timeout = false;
        let isTimeFormatError = false;
        let previousValue = $time.val();

        $time.on('change', () => {
            if (!timeout) {
                if (isTimeFormatError) {
                    $time.val(previousValue);

                    return;
                }

                if (this.noneOption && $time.val() === '' && this.$date.val() !== '') {
                    $time.val(this.noneOption);

                    return;
                }

                this.trigger('change');

                previousValue = $time.val();
            }

            timeout = true;

            setTimeout(() => timeout = false, 100);
        });

        $time.on('timeFormatError', () => {
            isTimeFormatError = true;

            setTimeout(() => isTimeFormatError = false, 50);
        });
    }

    /**
     * @param {string} string
     * @return {string|-1|null}
     */
    parse(string) {
        if (!string) {
            return null;
        }

        return this.getDateTime().fromDisplay(string);
    }

    fetch() {
        let data = {};

        let date = this.$date.val();
        let time = this.$time.val();

        let value = null;

        if (date !== '' && time !== '') {
            value = this.parse(date + ' ' + time);
        }

        data[this.name] = value;

        return data;
    }

    // noinspection JSUnusedGlobalSymbols
    validateDatetime() {
        if (this.model.get(this.name) === -1) {
            let msg = this.translate('fieldShouldBeDatetime', 'messages')
                .replace('{field}', this.getLabelText());

            this.showValidationMessage(msg);

            return true;
        }
    }

    /** @inheritDoc */
    fetchSearch() {
        let data = super.fetchSearch();

        if (data) {
            data.dateTime = true;
        }

        return data;
    }
}

export default DatetimeFieldView;
PK]���	��views/fields/float.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/fields/float */

import IntFieldView from 'views/fields/int';

/**
 * A float field.
 */
class FloatFieldView extends IntFieldView {

    type = 'float'

    editTemplate = 'fields/float/edit'

    decimalMark = '.'
    validations = ['required', 'float', 'range']
    decimalPlacesRawValue = 10

    /** @inheritDoc */
    setup() {
        super.setup();

        if (this.getPreferences().has('decimalMark')) {
            this.decimalMark = this.getPreferences().get('decimalMark');
        }
        else if (this.getConfig().has('decimalMark')) {
            this.decimalMark = this.getConfig().get('decimalMark');
        }

        if (!this.decimalMark) {
            this.decimalMark = '.';
        }

        if (this.decimalMark === this.thousandSeparator) {
            this.thousandSeparator = '';
        }
    }

    /** @inheritDoc */
    setupAutoNumericOptions() {
        this.autoNumericOptions = {
            digitGroupSeparator: this.thousandSeparator || '',
            decimalCharacter: this.decimalMark,
            modifyValueOnWheel: false,
            selectOnFocus: false,
            decimalPlaces: this.decimalPlacesRawValue,
            decimalPlacesRawValue: this.decimalPlacesRawValue,
            allowDecimalPadding: false,
            showWarnings: false,
            formulaMode: true,
        };
    }

    getValueForDisplay() {
        let value = isNaN(this.model.get(this.name)) ? null : this.model.get(this.name);

        return this.formatNumber(value);
    }

    formatNumber(value) {
        if (this.disableFormatting) {
            return value;
        }

        return this.formatNumberDetail(value);
    }

    formatNumberDetail(value) {
        if (value === null) {
            return '';
        }

        let decimalPlaces = this.params.decimalPlaces;

        if (decimalPlaces === 0) {
            value = Math.round(value);
        }
        else if (decimalPlaces) {
            value = Math.round(
                 value * Math.pow(10, decimalPlaces)) / (Math.pow(10, decimalPlaces)
            );
        }

        let parts = value.toString().split(".");

        parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, this.thousandSeparator);

        if (decimalPlaces === 0) {
            return parts[0];
        }
        else if (decimalPlaces) {
            var decimalPartLength = 0;

            if (parts.length > 1) {
                decimalPartLength = parts[1].length;
            } else {
                parts[1] = '';
            }

            if (decimalPlaces && decimalPartLength < decimalPlaces) {
                var limit = decimalPlaces - decimalPartLength;

                for (var i = 0; i < limit; i++) {
                    parts[1] += '0';
                }
            }
        }

        return parts.join(this.decimalMark);
    }

    setupMaxLength() {}

    validateFloat() {
        let value = this.model.get(this.name);

        if (isNaN(value)) {
            let msg = this.translate('fieldShouldBeFloat', 'messages')
                .replace('{field}', this.getLabelText());

            this.showValidationMessage(msg);

            return true;
        }
    }

    parse(value) {
        value = (value !== '') ? value : null;

        if (value === null) {
            return null;
        }

        value = value
            .split(this.thousandSeparator)
            .join('')
            .split(this.decimalMark)
            .join('.');

        return parseFloat(value);
    }

    fetch() {
        let value = this.$element.val();
        value = this.parse(value);

        let data = {};
        data[this.name] = value;

        return data;
    }
}

export default FloatFieldView;
PK]�v�&MMviews/fields/foreign-date.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import DateFieldView from 'views/fields/date';
import Helper from 'helpers/misc/foreign-field';

class ForeignDateFieldView extends DateFieldView {

    type = 'foreign'

    setup() {
        super.setup();

        const helper = new Helper(this);

        const foreignParams = helper.getForeignParams();

        for (let param in foreignParams) {
            this.params[param] = foreignParams[param];
        }
    }
}

export default ForeignDateFieldView;
PK]�n���views/fields/assigned-user.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import UserWithAvatarFieldView from 'views/fields/user-with-avatar';

class AssignedUserFieldView extends UserWithAvatarFieldView {

    init() {
        this.assignmentPermission = this.getAcl().getPermissionLevel('assignmentPermission');

        if (this.assignmentPermission === 'no') {
            this.setReadOnly(true);
        }

        super.init();
    }

    getSelectBoolFilterList() {
        if (this.assignmentPermission === 'team') {
            return ['onlyMyTeam'];
        }
    }

    getSelectPrimaryFilterName() {
        return 'active';
    }

    getEmptyAutocompleteResult() {
        return {
            list: [
                {
                    id: this.getUser().id,
                    name: this.getUser().get('name'),
                }
            ]
        };
    }
}

export default AssignedUserFieldView;
PK]��AAviews/fields/checklist.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/fields/checklist */

import ArrayFieldView from 'views/fields/array';

class ChecklistFieldView extends ArrayFieldView {

    type = 'checklist'

    listTemplate = 'fields/array/list'
    detailTemplate = 'fields/checklist/detail'
    editTemplate = 'fields/checklist/edit'

    isInversed = false

    events = {}

    data() {
        return {
            optionDataList: this.getOptionDataList(),
            ...super.data(),
        };
    }

    setup() {
        super.setup();

        this.params.options = this.params.options || [];

        this.isInversed = this.params.isInversed || this.options.isInversed || this.isInversed;
    }

    afterRender() {
        if (this.isSearchMode()) {
            this.renderSearch();
        }

        if (this.isEditMode()) {
            this.$el.find('input').on('change', () => {
                this.trigger('change');
            });
        }
    }

    getOptionDataList() {
        let valueList = this.model.get(this.name) || [];
        let list = [];

        this.params.options.forEach((item) => {
            let isChecked = ~valueList.indexOf(item);
            let dataName = item;
            let id = this.cid + '-' + Espo.Utils.camelCaseToHyphen(item.replace(/\s+/g, '-'));

            if (this.isInversed) {
                isChecked = !isChecked;
            }

            list.push({
                name: item,
                isChecked: isChecked,
                dataName: dataName,
                id: id,
                label: this.translatedOptions[item] || item,
            });
        });

        return list;
    }

    fetch() {
        let list = [];

        this.params.options.forEach(item => {
            let $item = this.$el.find('input[data-name="' + item + '"]');

            let isChecked = $item.get(0) && $item.get(0).checked;

            if (this.isInversed) {
                isChecked = !isChecked;
            }

            if (isChecked) {
                list.push(item);
            }
        });

        let data = {};

        data[this.name] = list;

        return data;
    }

    validateRequired() {
        if (!this.isRequired()) {
            return;
        }

        let value = this.model.get(this.name);

        if (!value || value.length === 0) {
            let msg = this.translate('fieldIsRequired', 'messages')
                .replace('{field}', this.getLabelText());

            this.showValidationMessage(msg, '.checklist-item-container:last-child input');

            return true;
        }
    }

    validateMaxCount() {
        if (!this.params.maxCount) {
            return;
        }

        let itemList = this.model.get(this.name) || [];

        if (itemList.length > this.params.maxCount) {
            let msg =
                this.translate('fieldExceedsMaxCount', 'messages')
                    .replace('{field}', this.getLabelText())
                    .replace('{maxCount}', this.params.maxCount.toString());

            this.showValidationMessage(msg, '.checklist-item-container:last-child input');

            return true;
        }
    }
}

export default ChecklistFieldView;
PK]H=�77views/fields/range-currency.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import RangeFloatFieldView from 'views/fields/range-float';
import CurrencyFieldView from 'views/fields/currency';
import Select from 'ui/select';

class RangeCurrencyFieldView extends RangeFloatFieldView {

    type = 'rangeCurrency'

    editTemplate = 'fields/range-currency/edit'

    data() {
        return {
            currencyField: this.currencyField,
            currencyValue: this.model.get(this.fromCurrencyField) ||
                this.getPreferences().get('defaultCurrency') ||
                this.getConfig().get('defaultCurrency'),
            currencyList: this.currencyList,
            ...super.data(),
        }
    }

    setup() {
        super.setup();

        const ucName = Espo.Utils.upperCaseFirst(this.name);

        this.fromCurrencyField = 'from' + ucName + 'Currency';
        this.toCurrencyField = 'to' + ucName + 'Currency';

        this.currencyField = this.name + 'Currency';
        this.currencyList = this.getConfig().get('currencyList') || ['USD'];
        this.decimalPlaces = this.getConfig().get('currencyDecimalPlaces');
    }

    setupAutoNumericOptions() {
        this.autoNumericOptions = {
            digitGroupSeparator: this.thousandSeparator || '',
            decimalCharacter: this.decimalMark,
            modifyValueOnWheel: false,
            selectOnFocus: false,
            decimalPlaces: this.decimalPlaces,
            allowDecimalPadding: true,
            showWarnings: false,
            formulaMode: true,
        };

        if (this.decimalPlaces === null) {
            this.autoNumericOptions.decimalPlaces = this.decimalPlacesRawValue;
            this.autoNumericOptions.decimalPlacesRawValue = this.decimalPlacesRawValue;
            this.autoNumericOptions.allowDecimalPadding = false;
        }
    }

    afterRender() {
        super.afterRender();

        if (this.mode === this.MODE_EDIT) {
            this.$currency = this.$el.find('[data-name="' + this.currencyField + '"]');

            Select.init(this.$currency);
        }
    }

    formatNumber(value) {
        return CurrencyFieldView.prototype.formatNumberDetail.call(this, value);
    }

    getValueForDisplay() {
        let fromValue = this.model.get(this.fromField);
        let toValue = this.model.get(this.toField);

        fromValue = isNaN(fromValue) ? null : fromValue;
        toValue = isNaN(toValue) ? null : toValue;

        let currencyValue = this.model.get(this.fromCurrencyField) ||
            this.model.get(this.toCurrencyField);

        let symbol = this.getMetadata().get(['app', 'currency', 'symbolMap', currencyValue]) || currencyValue;

        if (fromValue !== null && toValue !== null) {
            return this.formatNumber(fromValue) + ' &#8211 ' +
                this.formatNumber(toValue) + ' ' + symbol + '';
        }

        if (fromValue) {
            return '&#62;&#61; ' + this.formatNumber(fromValue) + ' ' + symbol+'';
        }

        if (toValue) {
            return '&#60;&#61; ' + this.formatNumber(toValue) + ' ' + symbol+'';
        }

        return this.translate('None');
    }

    fetch() {
        const data = super.fetch();

        let currencyValue = this.$currency.val();

        if (data[this.fromField] !== null) {
            data[this.fromCurrencyField] = currencyValue;
        }
        else {
            data[this.fromCurrencyField] = null;
        }

        if (data[this.toField] !== null) {
            data[this.toCurrencyField] = currencyValue;
        }
        else {
            data[this.toCurrencyField] = null;
        }

        return data;
    }
}

// noinspection JSUnusedGlobalSymbols
export default RangeCurrencyFieldView;
PK]ޮ7O��)views/fields/link-multiple-with-status.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import LinkMultipleFieldView from 'views/fields/link-multiple';

class LinkMultipleWithStatusFieldView extends LinkMultipleFieldView {

    setup() {
        super.setup();

        this.columnsName = this.name + 'Columns';
        this.columns = Espo.Utils.cloneDeep(this.model.get(this.columnsName) || {});

        this.listenTo(this.model, 'change:' + this.columnsName, () => {
            this.columns = Espo.Utils.cloneDeep(this.model.get(this.columnsName) || {});
        });

        this.statusField = this.getMetadata()
            .get(['entityDefs', this.model.entityType,  'fields', this.name, 'columns', 'status']);

        this.styleMap = this.getMetadata()
            .get(['entityDefs', this.foreignScope, 'fields', this.statusField, 'style']) || {};
    }

    getAttributeList() {
        const list = super.getAttributeList();

        list.push(this.name + 'Columns');

        return list;
    }

    getDetailLinkHtml(id, name) {
        let status = (this.columns[id] || {}).status;

        if (!status) {
            return super.getDetailLinkHtml(id, name);
        }

        let style = this.styleMap[status];

        let targetStyleList = ['success', 'danger'];

        if (!style || !~targetStyleList.indexOf(style)) {
            return super.getDetailLinkHtml(id, name);
        }

        let iconStyle = '';

        if (style === 'success') {
            iconStyle = 'fas fa-check text-success small';
        }
        else if (style === 'danger') {
            iconStyle = 'fas fa-times text-danger small';
        }

        return '<span class="' + iconStyle + '"></span> ' +
            super.getDetailLinkHtml(id, name);
    }
}

// noinspection JSUnusedGlobalSymbols
export default LinkMultipleWithStatusFieldView;
PK]!퀳�views/fields/enum-int.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import EnumFieldView from 'views/fields/enum';

class EnumIntFieldView extends EnumFieldView {

    type = 'enumInt'

    listTemplate = 'fields/enum/detail'
    detailTemplate = 'fields/enum/detail'
    editTemplate = 'fields/enum/edit'
    searchTemplate = 'fields/enum/search'

    validations = []

    fetch() {
        let value = parseInt(this.$element.val());
        let data = {};

        data[this.name] = value;

        return data;
    }

    parseItemForSearch(item) {
        return parseInt(item);
    }
}

export default EnumIntFieldView;
PK]ofR���views/fields/url.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/fields/url */

import VarcharFieldView from 'views/fields/varchar';

class UrlFieldView extends VarcharFieldView {

    type = 'url'

    listTemplate = 'fields/url/list'
    detailTemplate = 'fields/url/detail'
    defaultProtocol = 'https:'

    validations = [
        'required',
        'valid',
        'maxLength',
    ]

    noSpellCheck = true

    DEFAULT_MAX_LENGTH =255

    data() {
        const data = super.data();

        data.url = this.getUrl();

        return data;
    }

    afterRender() {
        super.afterRender();

        if (this.isEditMode()) {
            this.$element.on('change', () => {
                const value = this.$element.val() || '';

                const parsedValue = this.parse(value);

                if (parsedValue === value) {
                    return;
                }

                const decoded = parsedValue ? decodeURI(parsedValue) : '';

                this.$element.val(decoded);
            });
        }
    }

    getValueForDisplay() {
        const value = this.model.get(this.name);

        return value ? decodeURI(value) : null;
    }

    /**
     * @param {string} value
     * @return {string}
     */
    parse(value) {
        value = value.trim();

        if (this.params.strip) {
            value = this.strip(value);
        }

        if (value === decodeURI(value)) {
            value = encodeURI(value);
        }

        return value;
    }

    /**
     * @param {string} value
     * @return {string}
     */
    strip(value) {
        if (value.indexOf('//') !== -1) {
            value = value.substring(value.indexOf('//') + 2);
        }

        value = value.replace(/\/+$/, '');

        return value;
    }

    getUrl() {
        let url = this.model.get(this.name);

        if (url && url !== '') {
            if (url.indexOf('//') === -1) {
                url = this.defaultProtocol + '//' + url;
            }

            return url;
        }

        return url;
    }

    // noinspection JSUnusedGlobalSymbols
    validateValid() {
        const value = this.model.get(this.name);

        if (!value) {
            return false;
        }

        /** @var {string} */
        const pattern = this.getMetadata().get(['app', 'regExpPatterns', 'uriOptionalProtocol', 'pattern']);

        const regExp = new RegExp('^' + pattern + '$');

        if (regExp.test(value)) {
            return false;
        }

        const msg = this.translate('fieldInvalid', 'messages')
            .replace('{field}', this.translate(this.name, 'fields', this.entityType));

        this.showValidationMessage(msg);

        return true;
    }

    // noinspection JSUnusedGlobalSymbols
    validateMaxLength() {
        const maxLength = this.params.maxLength || this.DEFAULT_MAX_LENGTH;

        const value = this.model.get(this.name);

        if (!value || !value.length) {
            return false;
        }

        if (value.length <= maxLength) {
            return false;
        }

        const msg = this.translate('fieldUrlExceedsMaxLength', 'messages')
            .replace('{maxLength}', maxLength)
            .replace('{field}', this.translate(this.name, 'fields', this.entityType));

        this.showValidationMessage(msg);

        return true;
    }

    fetch() {
        const data = super.fetch();

        const value = data[this.name];

        if (!value) {
            return data;
        }

        data[this.name] = this.parse(value);

        return data;
    }
}

export default UrlFieldView;
PK]�QB�OMOM*views/fields/link-multiple-with-columns.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/fields/link-multiple-with-columns */

import LinkMultipleFieldView from 'views/fields/link-multiple';
import RegExpPattern from 'helpers/reg-exp-pattern';
import Select from 'ui/select';

/**
 * A link-multiple field with relation column(s).
 */
class LinkMultipleWithColumnsFieldView extends LinkMultipleFieldView {

    /** @const */
    COLUMN_TYPE_VARCHAR = 'varchar'
     /** @const */
    COLUMN_TYPE_ENUM = 'enum'
     /** @const */
    COLUMN_TYPE_BOOL = 'bool'

    /** @inheritDoc */
    setup() {
        super.setup();

        let columnsDefsInitial = this.columnsDefs || {};

        this.validations.push('columnPattern');

        /**
         * @type {Object.<string,*>}
         */
        this.columnsDefs = {};
        this.columnsName = this.name + 'Columns';
        this.columns = Espo.Utils.cloneDeep(this.model.get(this.columnsName) || {});

        this.listenTo(this.model, 'change:' + this.columnsName, () => {
            this.columns = Espo.Utils.cloneDeep(this.model.get(this.columnsName) || {});
        });

        let columns = this.getMetadata()
            .get(['entityDefs', this.model.entityType, 'fields', this.name, 'columns']) || {};

        /** @type {string[]} */
        this.columnList = this.columnList || Object.keys(columns);

        this.columnList.forEach(column => {
            if (column in columnsDefsInitial) {
                this.columnsDefs[column] = Espo.Utils.cloneDeep(columnsDefsInitial[column]);

                return;
            }

            if (column in columns) {
                let field = columns[column];

                let o = {};

                o.field = field;
                o.scope = this.foreignScope;

                if (
                    !this.getMetadata().get(['entityDefs', this.foreignScope, 'fields', field, 'type']) &&
                    this.getMetadata().get(['entityDefs', this.model.entityType, 'fields', field, 'type'])
                ) {
                    o.scope = this.model.entityType;
                }

                let fieldDefs = this.getMetadata().get(['entityDefs', o.scope, 'fields', field]) || {};

                o.type = fieldDefs.type;

                if (o.type === this.COLUMN_TYPE_ENUM || o.type === this.COLUMN_TYPE_VARCHAR) {
                    o.options = fieldDefs.options;
                }

                if ('default' in fieldDefs) {
                    o.default = fieldDefs.default;
                }

                if ('maxLength' in fieldDefs) {
                    o.maxLength = fieldDefs.maxLength;
                }

                if ('pattern' in fieldDefs) {
                    o.pattern = fieldDefs.pattern;
                }

                this.columnsDefs[column] = o;
            }
        });

        if (this.isEditMode() || this.isDetailMode()) {
            this.events['click a[data-action="toggleBoolColumn"]'] = (e) => {
                let id = $(e.currentTarget).data('id');
                let column = $(e.currentTarget).data('column');

                this.toggleBoolColumn(id, column);
            };
        }

        this.on('render', this.disposeColumnAutocompletes, this);
        this.once('remove', this.disposeColumnAutocompletes, this);
    }

    toggleBoolColumn(id, column) {
        this.columns[id][column] = !this.columns[id][column];

        this.reRender();
    }

    /** @inheritDoc */
    getAttributeList() {
        return [
            ...super.getAttributeList(),
            this.name + 'Columns'
        ];
    }

    /**
     * Get an item HTML for detail mode.
     *
     * @param {string} id An ID.
     * @param {string} [name] An name.
     * @return {string}
     */
    getDetailLinkHtml(id, name) {
        // Do not use the `html` method to avoid XSS.

        name = name || this.nameHash[id] || id;

        let $el = $('<div>')
            .append(
                $('<a>')
                    .attr('href', '#' + this.foreignScope + '/view/' + id)
                    .attr('data-id', id)
                    .text(name)
            );

        if (this.isDetailMode()) {
            let iconHtml = this.getIconHtml(id);

            if (iconHtml) {
                $el.prepend(iconHtml);
            }
        }

        this.columnList.forEach(column => {
            let value = (this.columns[id] || {})[column] || '';
            let type = this.columnsDefs[column].type;
            let field = this.columnsDefs[column].field;
            let scope = this.columnsDefs[column].scope;

            if (value === '' || !value) {
                return;
            }

            if (type !== this.COLUMN_TYPE_ENUM && type !== this.COLUMN_TYPE_VARCHAR) {
                return;
            }

            let text = type === this.COLUMN_TYPE_ENUM ?
                this.getLanguage().translateOption(value, field, scope) :
                value;

            $el.append(
                $('<span>').text(' '),
                $('<span>').addClass('text-muted chevron-right'),
                $('<span>').text(' '),
                $('<span>').text(text).addClass('text-muted small')
            );
        });

        return $el.get(0).outerHTML;
    }

    /** @inheritDoc */
    getValueForDisplay() {
        if (this.isDetailMode() || this.isListMode()) {
            let itemList = [];

            this.ids.forEach(id => {
                itemList.push(
                    this.getDetailLinkHtml(id)
                );
            });

            return itemList.join('');
        }
    }

    /** @inheritDoc */
    deleteLink(id) {
        this.trigger('delete-link', id);
        this.trigger('delete-link:' + id);

        this.deleteLinkHtml(id);

        let index = this.ids.indexOf(id);

        if (index > -1) {
            this.ids.splice(index, 1);
        }

        delete this.nameHash[id];
        delete this.columns[id];

        this.afterDeleteLink(id);

        this.trigger('change');
    }

    /**
     * Get a column values.
     *
     * @param {string} id An ID.
     * @param {string} column A column.
     * @return {*}
     */
    getColumnValue(id, column) {
        return (this.columns[id] || {})[column];
    }

    addLink(id, name) {
        if (!~this.ids.indexOf(id)) {
            this.ids.push(id);
            this.nameHash[id] = name;
            this.columns[id] = {};

            this.columnList.forEach(column => {
                this.columns[id][column] = null;

                if ('default' in this.columnsDefs[column]) {
                    this.columns[id][column] = this.columnsDefs[column].default;
                }
            });

            this.addLinkHtml(id, name);

            this.afterAddLink(id);

            this.trigger('add-link', id);
            this.trigger('add-link:' + id);
        }

        this.trigger('change');
    }

    /**
     * @param {string} column
     * @param {string} id
     * @param {*} value
     * @return {JQuery}
     */
    getJQSelect(column, id, value) {
        // Do not use the `html` method to avoid XSS.

        let field = this.columnsDefs[column].field;
        let scope = this.columnsDefs[column].scope;
        let options = this.columnsDefs[column].options || [];

        let $select = $('<select>')
            .addClass('role form-control input-sm')
            .attr('data-id', id)
            .attr('data-column', column);

        options.forEach(itemValue => {
            let text = this.getLanguage().translateOption(itemValue, field, scope);

            let $option = $('<option>')
                .val(itemValue)
                .text(text);

            if (itemValue === (value || '')) {
                $option.attr('selected', 'selected');
            }

            $select.append($option);
        })

        return $select;
    }

    /**
     * @param {string} column
     * @param {string} id
     * @param {*} value
     * @return {JQuery}
     */
    getJQInput(column, id, value) {
        // Do not use the `html` method to avoid XSS.

        let field = this.columnsDefs[column].field;
        let scope = this.columnsDefs[column].scope;
        let maxLength = this.columnsDefs[column].maxLength;

        let text = this.translate(field, 'fields', scope);

        let $input = $('<input>')
            .addClass('role form-control input-sm')
            .attr('data-column', column)
            .attr('placeholder', text)
            .attr('data-id', id)
            .attr('value', value || '');

        if (maxLength) {
            $input.attr('maxlength', maxLength);
        }

        return $input;
    }

    /**
     * @param {string} column
     * @param {string} id
     * @param {*} value
     * @return {JQuery}
     */
    getJQLi(column, id, value) {
        // Do not use the `html` method to avoid XSS.

        let field = this.columnsDefs[column].field;
        let scope = this.columnsDefs[column].scope;

        let text = this.translate(field, 'fields', scope);

        return $('<li>')
            .append(
                $('<a>')
                    .attr('role', 'button')
                    .attr('tabindex', '0')
                    .attr('data-action', 'toggleBoolColumn')
                    .attr('data-column', column)
                    .attr('data-id', id)
                    .append(
                        $('<span>')
                            .addClass('check-icon fas fa-check pull-right')
                            .addClass(!value ? 'hidden' : '')
                    )
                    .append(
                        $('<div>').text(text)
                    )
            );
    }

    /** @inheritDoc */
    addLinkHtml(id, name) {
        if (this.isSearchMode()) {
            return super.addLinkHtml(id, name);
        }

        // Do not use the `html` method to avoid XSS.

        let $container = this.$el.find('.link-container');

        let $el = $('<div>')
            .addClass('form-inline clearfix')
            .addClass('list-group-item link-with-role link-group-item-with-columns')
            .addClass('link-' + id);

        let $remove = $('<a>')
            .attr('role', 'button')
            .attr('tabindex', '0')
            .attr('data-id', id)
            .attr('data-action', 'clearLink')
            .addClass('pull-right')
            .append(
                $('<span>').addClass('fas fa-times')
            );

        let $name = $('<div>')
            .addClass('link-item-name')
            .text(name)
            .append('&nbsp;')

        let $columnList = [];
        let $liList = [];

        this.columnList.forEach(column => {
            let value = (this.columns[id] || {})[column];

            let type = this.columnsDefs[column].type;

            if (type === this.COLUMN_TYPE_ENUM) {
                $columnList.push(
                    this.getJQSelect(column, id, value)
                );

                return;
            }

            if (type === this.COLUMN_TYPE_VARCHAR) {
                $columnList.push(
                    this.getJQInput(column, id, value)
                );

                return;
            }

            if (type === this.COLUMN_TYPE_BOOL) {
                $liList.push(
                    this.getJQLi(column, id, value)
                );
            }
        });

        let $left = $('<div>');
        let $right = $('<div>');

        $columnList.forEach($item => $left.append(
            $('<span>')
                .addClass('link-item-column')
                .addClass('link-item-column-' + $item.get(0).tagName.toLowerCase())
                .append($item)
        ));

        if ($liList.length) {
            let $ul = $('<ul>').addClass('dropdown-menu');

            $liList.forEach($item => $ul.append($item));

            $left.append(
                $('<div>')
                    .addClass('btn-group pull-right')
                    .append(
                        $('<button>')
                            .attr('type', 'button')
                            .attr('data-toggle', 'dropdown')
                            .addClass('btn btn-link btn-sm dropdown-toggle')
                            .append(
                                $('<span>').addClass('caret')
                            )
                    )
                    .append($ul)
            );
        }

        $left.append($name);
        $right.append($remove);

        $el.append($left);
        $el.append($right);

        $container.append($el);

        if (this.isEditMode()) {
            $columnList.forEach($column => {

                if ($column.get(0).tagName === 'SELECT') {
                    Select.init($column);
                }

                let fetch = ($target) => {
                    if (!$target || !$target.length) {
                        return;
                    }

                    let column = $target.data('column');
                    let value = $target.val().toString().trim();
                    let id = $target.data('id');

                    if (value === '') {
                        value = null;
                    }

                    this.columns[id] = this.columns[id] || {};
                    this.columns[id][column] = value;
                };

                $column.on('change', e => {
                    let $target = $(e.currentTarget);

                    fetch($target);
                    this.trigger('change');
                });

                fetch($column);
            });

            this.initAutocomplete(id);
        }

        return $el;
    }

    initAutocomplete(id) {
        if (!this._autocompleteElementList) {
            this._autocompleteElementList = [];
        }

        this.columnList.forEach(column => {
            let type = this.columnsDefs[column].type;

            if (type === this.COLUMN_TYPE_VARCHAR) {
                let options = this.columnsDefs[column].options;

                if (options && options.length) {
                    let $element = this.$el.find('[data-column="'+column+'"][data-id="'+id+'"]');

                    if (!$element.length) {
                        return;
                    }

                    $element.autocomplete({
                        minChars: 0,
                        lookup: options,
                        maxHeight: 200,
                        beforeRender: (c) => {
                            c.addClass('small');
                        },
                        formatResult: (suggestion) => {
                            return this.getHelper().escapeString(suggestion.value);
                        },
                        lookupFilter: (suggestion, query, queryLowerCase) => {
                            if (suggestion.value.toLowerCase().indexOf(queryLowerCase) === 0) {
                                if (suggestion.value.length === queryLowerCase.length) {
                                    return false;
                                }

                                return true;
                            }

                            return false;
                        },
                        onSelect: () => {
                            this.trigger('change');
                            $element.trigger('change');
                            $element.focus();
                        },
                    });

                    $element.attr('autocomplete', 'espo-' + this.name + '-' + column + '-' + id);

                    $element.on('focus', () => {
                        if ($element.val()) {
                            return;
                        }

                        $element.autocomplete('onValueChange');
                    });

                    this._autocompleteElementList.push($element);

                    this.once('delete-link:' + id, () => {
                        $element.autocomplete('dispose');
                    });
                }
            }
        });
    }

    disposeColumnAutocompletes() {
        if (this._autocompleteElementList && this._autocompleteElementList.length) {
            this._autocompleteElementList.forEach($el =>{
                $el.autocomplete('dispose');
            });

            this._autocompleteElementList = [];
        }
    }

    // noinspection JSUnusedGlobalSymbols
    validateColumnPattern() {
        let result = false;

        let columnList = this.columnList
            .filter(column => this.columnsDefs[column].type === this.COLUMN_TYPE_VARCHAR)
            .filter(column => this.columnsDefs[column].pattern);

        for (let column of columnList) {
            for (let id of this.ids) {
                let value = this.getColumnValue(id, column);

                if (!value) {
                    continue;
                }

                if (this.validateColumnPatternValue(id, column, value)) {
                    result = true;
                }
            }
        }

        return result;
    }

    validateColumnPatternValue(id, column, value) {
        let pattern = this.columnsDefs[column].pattern;
        let field = this.columnsDefs[column].field;
        let scope = this.columnsDefs[column].scope;

        let helper = new RegExpPattern(this.getMetadata(), this.getLanguage());

        let result = helper.validate(pattern, value, field, scope);

        if (!result) {
            return false;
        }

        this.showValidationMessage(result.message, '[data-column="' + column + '"][data-id="' + id + '"]');

        return true;
    }

    fetch() {
        let data = super.fetch();

        data[this.columnsName] = Espo.Utils.cloneDeep(this.columns);

        // noinspection JSValidateTypes
        return data;
    }
}

export default LinkMultipleWithColumnsFieldView;
PK]�?;Z�*�*'views/fields/link-multiple-with-role.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import LinkMultipleFieldView from 'views/fields/link-multiple';
import Select from 'ui/select';

/**
 * A link-multiple field with a relation column.
 * @deprecated Prefer using `link-multiple-with-columns` instead.
 */
class LinkMultipleWithRoleFieldView extends LinkMultipleFieldView {

    /**
     * A role field type.
     */
    roleType = 'enum'
    /**
     * A relation column name.
     */
    columnName = 'role'
    /**
     * The role field is defined in a foreign entity.
     */
    roleFieldIsForeign = true
    /**
     * A value to fetch for an empty role.
     */
    emptyRoleValue = null
    /**
     * A role placeholder text.
     */
    rolePlaceholderText = null
    /**
     * A role value max length.
     * @protected
     */
    roleMaxLength = 50

    /** @const */
    ROLE_TYPE_ENUM = 'enum'
    // noinspection JSUnusedGlobalSymbols
    /** @const */
    ROLE_TYPE_VARCHAR = 'varchar'

    setup() {
        super.setup();

        this.columnsName = this.name + 'Columns';
        this.columns = Espo.Utils.cloneDeep(this.model.get(this.columnsName) || {});

        this.listenTo(this.model, 'change:' + this.columnsName, () => {
            this.columns = Espo.Utils.cloneDeep(this.model.get(this.columnsName) || {});
        });

        this.roleField = this.getMetadata()
            .get(['entityDefs', this.model.entityType, 'fields', this.name, 'columns', this.columnName]);

        this.displayRoleAsLabel = this.getMetadata()
            .get(['entityDefs', this.model.entityType, 'fields', this.roleField, 'displayAsLabel']);

        this.roleFieldScope = this.roleFieldIsForeign ? this.foreignScope : this.model.entityType;

        if (this.roleType === this.ROLE_TYPE_ENUM && !this.forceRoles) {
            this.roleList = this.getMetadata()
                .get(['entityDefs', this.roleFieldScope, 'fields', this.roleField, 'options']);

            if (!this.roleList) {
                this.roleList = [];
                this.skipRoles = true;
            }
        }
    }

    getAttributeList() {
        const list = super.getAttributeList();

        list.push(this.name + 'Columns');

        return list;
    }

    getDetailLinkHtml(id, name) {
        // Do not use the `html` method to avoid XSS.

        name = name || this.nameHash[id] || id;

        if (!name && id) {
            name = this.translate(this.foreignScope, 'scopeNames');
        }

        let role = (this.columns[id] || {})[this.columnName] || '';

        if (this.emptyRoleValue && role === this.emptyRoleValue) {
            role = '';
        }

        let $el = $('<div>')
            .append(
                $('<a>')
                    .attr('href', '#' + this.foreignScope + '/view/' + id)
                    .attr('data-id', id)
                    .text(name)
            );

        if (this.isDetailMode()) {
            let iconHtml = this.getIconHtml(id);

            if (iconHtml) {
                $el.prepend(iconHtml);
            }
        }

        if (role) {
            let style = this.getMetadata()
                .get(['entityDefs', this.model.entityType, 'fields', this.roleField, 'style', role]);

            let className = 'text';

            if (this.displayRoleAsLabel && style && style !== 'default') {
                className = 'label label-sm label';

                if (style === 'muted') {
                    style = 'default';
                }
            } else {
                style = style || 'muted';
            }

            className = className + '-' + style;

            let text = this.roleType === this.ROLE_TYPE_ENUM ?
                this.getLanguage().translateOption(role, this.roleField, this.roleFieldScope) :
                role;

            $el.append(
                $('<span>').text(' '),
                $('<span>').addClass('text-muted chevron-right'),
                $('<span>').text(' '),
                $('<span>').text(text).addClass('small').addClass(className)
            );
        }

        return $el.get(0).outerHTML;
    }

    getValueForDisplay() {
        if (this.isDetailMode() || this.isListMode()) {
            let names = [];

            this.ids.forEach(id => {
                names.push(
                    this.getDetailLinkHtml(id)
                );
            });

            return names.join('');
        }
    }

    deleteLink(id) {
        this.trigger('delete-link', id);
        this.trigger('delete-link:' + id);

        this.deleteLinkHtml(id);

        let index = this.ids.indexOf(id);

        if (index > -1) {
            this.ids.splice(index, 1);
        }

        delete this.nameHash[id];
        delete this.columns[id];

        this.afterDeleteLink(id);
        this.trigger('change');
    }

    addLink(id, name) {
        if (!~this.ids.indexOf(id)) {
            this.ids.push(id);
            this.nameHash[id] = name;
            this.columns[id] = {};
            this.columns[id][this.columnName] = null;
            this.addLinkHtml(id, name);

            this.trigger('add-link', id);
            this.trigger('add-link:' + id);
        }

        this.trigger('change');
    }


    /**
     * Build a role select element.
     *
     * @param {string} id
     * @param {string|null} roleValue
     * @return {JQuery}
     */
    getJQSelect(id, roleValue) {
        // Do not use the `html` method to avoid XSS.

        let $role = $('<select>')
            .addClass('role form-control input-sm')
            .attr('data-id', id);

        this.roleList.forEach(role => {
            let text = this.getLanguage().translateOption(role, this.roleField, this.roleFieldScope);

            let $option = $('<option>')
                .val(role)
                .text(text);

            if (role === (roleValue || '')) {
                $option.attr('selected', 'selected');
            }

            $role.append($option);
        });

        return $role;
    }

    /**
     * @inheritDoc
     */
    addLinkHtml(id, name) {
        // Do not use the `html` method to avoid XSS.

        name = name || id;

        if (this.isSearchMode() || this.skipRoles) {
            return super.addLinkHtml(id, name);
        }

        let role = (this.columns[id] || {})[this.columnName];

        let $container = this.$el.find('.link-container');

        let $el = $('<div>')
            .addClass('form-inline clearfix')
            .addClass('list-group-item link-with-role link-group-item-with-columns')
            .addClass('link-' + id);

        let $remove = $('<a>')
            .attr('role', 'button')
            .attr('tabindex', '0')
            .attr('data-id', id)
            .attr('data-action', 'clearLink')
            .addClass('pull-right')
            .append(
                $('<span>').addClass('fas fa-times')
            );

        let $left = $('<div>').addClass('pull-left');
        let $right = $('<div>').append($remove);

        let $name = $('<div>')
            .addClass('link-item-name')
            .text(name)
            .append('&nbsp;')

        let $role;

        if (this.roleType === this.ROLE_TYPE_ENUM) {
            $role = this.getJQSelect(id, role);
        }
        else {
            let text = this.rolePlaceholderText || this.translate(this.roleField, 'fields', this.roleFieldScope);

            $role = $('<input>')
                .addClass('role form-control input-sm')
                .attr('maxlength', this.roleMaxLength) // @todo Get the value from metadata.
                .attr('placeholder', text)
                .attr('data-id', id)
                .attr('value', role || '');
        }

        if ($role) {
            $left.append($('<span>')
                .addClass('link-item-column')
                .addClass('link-item-column-' + $role.get(0).tagName.toLowerCase())
                .append($role)
            );
        }

        $left.append($name);
        $el.append($left).append($right);
        $container.append($el);

        if ($role && $role.get(0).tagName === 'SELECT') {
            Select.init($role);
        }

        if (this.isEditMode() && $role) {
            let fetch = ($target) => {
                if (!$target || !$target.length) {
                    return;
                }

                if ($target.val() === null) {
                    return;
                }

                let value = $target.val().toString().trim();
                let id = $target.data('id');

                if (value === '') {
                    value = null;
                }

                this.columns[id] = this.columns[id] || {};
                this.columns[id][this.columnName] = value;
            };

            $role.on('change', e => {
                fetch($(e.currentTarget));
                this.trigger('change');
            });

            fetch($role);
        }

        return $el;
    }

    fetch() {
        let data = super.fetch();

        if (!this.skipRoles) {
            data[this.columnsName] = Espo.Utils.cloneDeep(this.columns);
        }

        return data;
    }
}

// noinspection JSDeprecatedSymbols
export default LinkMultipleWithRoleFieldView;
PK]��:

views/fields/foreign.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import BaseFieldView from 'views/fields/base';

class ForeignFieldView extends BaseFieldView {

    type = 'foreign'
}

export default ForeignFieldView;
PK]�����views/fields/autoincrement.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import IntFieldView from 'views/fields/int';

class AutoincrementFieldView extends IntFieldView {

    type = 'autoincrement'

    validations = []

    inlineEditDisabled = true
    readOnly = true
    disableFormatting = true

    parse(value) {
        value = (value !== '') ? value : null;

        if (value !== null) {
            value = value.indexOf('.') !== -1 || value.indexOf(',') !== -1 ?
                NaN :
                parseInt(value);
        }

        return value;
    }

    fetch() {
        return {};
    }
}

export default AutoincrementFieldView;
PK]�(�,,views/fields/foreign-email.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import EmailFieldView from 'views/fields/email';

class ForeignEmailFieldView extends EmailFieldView {

    type = 'foreign'
    readOnly = true
}

export default ForeignEmailFieldView;
PK]�=��h"h"views/fields/currency.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/fields/currency */

import FloatFieldView from 'views/fields/float';
import Select from 'ui/select';

/**
 * A currency field.
 */
class CurrencyFieldView extends FloatFieldView {

    type = 'currency'

    editTemplate = 'fields/currency/edit'
    detailTemplate = 'fields/currency/detail'
    detailTemplate1 = 'fields/currency/detail-1'
    detailTemplate2 = 'fields/currency/detail-2'
    detailTemplate3 = 'fields/currency/detail-3'
    listTemplate = 'fields/currency/list'
    listTemplate1 = 'fields/currency/list-1'
    listTemplate2 = 'fields/currency/list-2'
    listTemplate3 = 'fields/currency/list-3'
    detailTemplateNoCurrency = 'fields/currency/detail-no-currency'

    maxDecimalPlaces = 3

    validations = [
        'required',
        'number',
        'range',
    ]

    /** @inheritDoc */
    data() {
        let currencyValue = this.model.get(this.currencyFieldName) ||
            this.getPreferences().get('defaultCurrency') ||
            this.getConfig().get('defaultCurrency');

        let multipleCurrencies = !this.isSingleCurrency || currencyValue !== this.defaultCurrency;

        return {
            ...super.data(),
            currencyFieldName: this.currencyFieldName,
            currencyValue: currencyValue,
            currencyOptions: this.currencyOptions,
            currencyList: this.currencyList,
            currencySymbol: this.getMetadata().get(['app', 'currency', 'symbolMap', currencyValue]) || '',
            multipleCurrencies: multipleCurrencies,
            defaultCurrency: this.defaultCurrency,
        };
    }

    /** @inheritDoc */
    setup() {
        super.setup();

        this.currencyFieldName = this.name + 'Currency';
        this.defaultCurrency = this.getConfig().get('defaultCurrency');
        this.currencyList = this.getConfig().get('currencyList') || [this.defaultCurrency];
        this.decimalPlaces = this.getConfig().get('currencyDecimalPlaces');

        if (this.params.onlyDefaultCurrency) {
            this.currencyList = [this.defaultCurrency];
        }

        this.isSingleCurrency = this.currencyList.length <= 1;

        let currencyValue = this.currencyValue = this.model.get(this.currencyFieldName) ||
            this.defaultCurrency;

        if (!~this.currencyList.indexOf(currencyValue)) {
            this.currencyList = Espo.Utils.clone(this.currencyList);
            this.currencyList.push(currencyValue);
        }
    }

    /** @inheritDoc */
    setupAutoNumericOptions() {
        this.autoNumericOptions = {
            digitGroupSeparator: this.thousandSeparator || '',
            decimalCharacter: this.decimalMark,
            modifyValueOnWheel: false,
            selectOnFocus: false,
            decimalPlaces: this.decimalPlaces,
            allowDecimalPadding: true,
            showWarnings: false,
            formulaMode: true,
        };

        if (this.decimalPlaces === null) {
            this.autoNumericOptions.decimalPlaces = this.decimalPlacesRawValue;
            this.autoNumericOptions.decimalPlacesRawValue = this.decimalPlacesRawValue;
            this.autoNumericOptions.allowDecimalPadding = false;
        }
    }

    getCurrencyFormat() {
        return this.getConfig().get('currencyFormat') || 1;
    }

    _getTemplateName() {
        if (this.mode === this.MODE_DETAIL || this.mode === this.MODE_LIST) {
            var prop;

            if (this.mode === this.MODE_LIST) {
                prop = 'listTemplate' + this.getCurrencyFormat().toString();
            }
            else {
                prop = 'detailTemplate' + this.getCurrencyFormat().toString();
            }

            if (this.options.hideCurrency) {
                prop = 'detailTemplateNoCurrency';
            }

            if (prop in this) {
                return this[prop];
            }
        }

        return super._getTemplateName();
    }

    formatNumber(value) {
        return this.formatNumberDetail(value);
    }

    formatNumberDetail(value) {
        if (value !== null) {
            let currencyDecimalPlaces = this.decimalPlaces;

            if (currencyDecimalPlaces === 0) {
                value = Math.round(value);
            }
            else if (currencyDecimalPlaces) {
                value = Math.round(
                    value * Math.pow(10, currencyDecimalPlaces)) / (Math.pow(10, currencyDecimalPlaces)
                );
            }
            else {
                value = Math.round(
                    value * Math.pow(10, this.maxDecimalPlaces)) / (Math.pow(10, this.maxDecimalPlaces)
                );
            }

            let parts = value.toString().split(".");

            parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, this.thousandSeparator);

            if (currencyDecimalPlaces === 0) {
                return parts[0];
            }
            else if (currencyDecimalPlaces) {
                let decimalPartLength = 0;

                if (parts.length > 1) {
                    decimalPartLength = parts[1].length;
                } else {
                    parts[1] = '';
                }

                if (currencyDecimalPlaces && decimalPartLength < currencyDecimalPlaces) {
                    let limit = currencyDecimalPlaces - decimalPartLength;

                    for (let i = 0; i < limit; i++) {
                        parts[1] += '0';
                    }
                }
            }

            return parts.join(this.decimalMark);
        }

        return '';
    }

    parse(value) {
        value = (value !== '') ? value : null;

        if (value === null) {
            return null;
        }

        value = value.split(this.thousandSeparator).join('');
        value = value.split(this.decimalMark).join('.');

        if (!this.params.decimal) {
            value = parseFloat(value);
        }

        return value;
    }

    afterRender() {
        super.afterRender();

        if (this.mode === this.MODE_EDIT) {
            this.$currency = this.$el.find('[data-name="' + this.currencyFieldName + '"]');

            this.$currency.on('change', () => {
                this.model.set(this.currencyFieldName, this.$currency.val(), {ui: true});
            });

            Select.init(this.$currency);
        }
    }

    validateNumber() {
        if (!this.params.decimal) {
            return this.validateFloat();
        }

        let value = this.model.get(this.name);

        if (Number.isNaN(Number(value))) {
            let msg = this.translate('fieldShouldBeNumber', 'messages').replace('{field}', this.getLabelText());

            this.showValidationMessage(msg);

            return true;
        }
    }

    fetch() {
        let value = this.$element.val().trim();

        value = this.parse(value);

        let data = {};

        let currencyValue = this.$currency.length ?
            this.$currency.val() :
            this.defaultCurrency;

        if (value === null) {
            currencyValue = null;
        }

        data[this.name] = value;
        data[this.currencyFieldName] = currencyValue;

        return data;
    }
}

export default CurrencyFieldView;
PK]�
���views/fields/datetime-short.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/fields/datetime-short */

import DatetimeFieldView from 'views/fields/datetime';
import moment from 'moment';

class DatetimeShortFieldView extends DatetimeFieldView {

    listTemplate = 'fields/datetime-short/list'
    detailTemplate = 'fields/datetime-short/detail'

    data() {
        let data = super.data();

        if (this.mode === this.MODE_LIST || this.mode === this.MODE_DETAIL) {
            data.fullDateValue = super.getDateStringValue();
        }

        return data;
    }

    getDateStringValue() {
        if (!(this.mode === this.MODE_LIST || this.mode === this.MODE_DETAIL)) {
            return super.getDateStringValue();
        }

        let value = this.model.get(this.name)

        if (!value) {
            return super.getDateStringValue();
        }

        let timeFormat = this.getDateTime().timeFormat;

        if (this.params.hasSeconds) {
            timeFormat = timeFormat.replace(/:mm/, ':mm:ss');
        }

        let m = this.getDateTime().toMoment(value);
        let now = moment().tz(this.getDateTime().timeZone || 'UTC');

        if (
            m.unix() > now.clone().startOf('day').unix() &&
            m.unix() < now.clone().add(1, 'days').startOf('day').unix()
        ) {
            return m.format(timeFormat);
        }

        let readableFormat = this.getDateTime().getReadableShortDateFormat();

        return m.format('YYYY') === now.format('YYYY') ?
            m.format(readableFormat) :
            m.format(readableFormat + ', YY');
    }
}

// noinspection JSUnusedGlobalSymbols
export default DatetimeShortFieldView;
PK]-d]=�r�r#views/fields/attachment-multiple.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/fields/attachment-multiple */

import BaseFieldView from 'views/fields/base';
import FileUpload from 'helpers/file-upload';

/**
 * An attachment-multiple field.
 */
class AttachmentMultipleFieldView extends BaseFieldView {

    type = 'attachmentMultiple'

    listTemplate = 'fields/attachments-multiple/list'
    detailTemplate = 'fields/attachments-multiple/detail'
    editTemplate = 'fields/attachments-multiple/edit'
    searchTemplate = 'fields/link-multiple/search'

    previewSize = 'medium'
    nameHashName = null
    idsName = null
    nameHash = null
    foreignScope = null
    showPreviews = true
    accept = null
    validations = ['ready', 'required']
    searchTypeList = ['isNotEmpty', 'isEmpty']

    events = {
        /** @this AttachmentMultipleFieldView */
        'click a.remove-attachment': function (e) {
            let $div = $(e.currentTarget).parent();

            let id = $div.attr('data-id');

            if (id) {
                this.deleteAttachment(id);
            }

            $div.parent().remove();

            this.$el.find('input.file').val(null);

            setTimeout(() => this.focusOnUploadButton(), 10);
        },
        /** @this AttachmentMultipleFieldView */
        'change input.file': function (e) {
            let $file = $(e.currentTarget);
            let files = e.currentTarget.files;

            this.uploadFiles(files);

            e.target.value = null;

            $file.replaceWith($file.clone(true));
        },
        /** @this AttachmentMultipleFieldView */
        'click a.action[data-action="insertFromSource"]': function (e) {
            let name = $(e.currentTarget).data('name');

            this.insertFromSource(name);
        },
        /** @this AttachmentMultipleFieldView */
        'click a[data-action="showImagePreview"]': function (e) {
            e.preventDefault();

            let id = $(e.currentTarget).data('id');

            let attachmentIdList = this.model.get(this.idsName) || [];
            let typeHash = this.model.get(this.typeHashName) || {};

            let imageIdList = [];

            attachmentIdList.forEach(cId => {
                if (!this.isTypeIsImage(typeHash[cId])) {
                    return;
                }

                imageIdList.push(cId);
            });

            let imageList = [];

            imageIdList.forEach((cId) => {
                imageList.push({
                    id: cId,
                    name: this.nameHash[cId]
                });
            });

            this.createView('preview', 'views/modals/image-preview', {
                id: id,
                model: this.model,
                name: this.nameHash[id],
                imageList: imageList,
            }, view => {
                view.render();
            });
        },
        /** @this AttachmentMultipleFieldView */
        'keydown label.attach-file-label': function (e) {
            let key = Espo.Utils.getKeyFromKeyEvent(e);

            if (key === 'Enter') {
                this.$el.find('input.file').get(0).click();
            }
        },
    }

    data() {
        let ids = this.model.get(this.idsName);

        let data = {
            ...super.data(),
            idValues: this.model.get(this.idsName),
            idValuesString: ids ? ids.join(',') : '',
            nameHash: this.model.get(this.nameHashName),
            foreignScope: this.foreignScope,
            valueIsSet: this.model.has(this.idsName),
            acceptAttribute: this.acceptAttribute,
        };

        if (this.mode === this.MODE_EDIT) {
            data.fileSystem = ~this.sourceList.indexOf('FileSystem');
            data.sourceList = this.sourceList;
        }

        return data;
    }

    setup() {
        this.nameHashName = this.name + 'Names';
        this.typeHashName = this.name + 'Types';
        this.idsName = this.name + 'Ids';
        this.foreignScope = 'Attachment';

        this.previewSize = this.options.previewSize || this.params.previewSize || this.previewSize;

        this.previewTypeList = this.getMetadata().get(['app', 'image', 'previewFileTypeList']) || [];
        this.imageSizes = this.getMetadata().get(['app', 'image', 'sizes']) || {};

        this.nameHash = _.clone(this.model.get(this.nameHashName)) || {};

        if ('showPreviews' in this.params) {
            this.showPreviews = this.params.showPreviews;
        }

        if ('accept' in this.params) {
            this.accept = this.params.accept;
        }

        if (this.accept && this.accept.length) {
            this.acceptAttribute = this.accept.join(', ');
        }

        let sourceDefs = this.getMetadata().get(['clientDefs', 'Attachment', 'sourceDefs']) || {};

        this.sourceList = Espo.Utils.clone(this.params.sourceList || []);

        this.sourceList = this.sourceList
            .concat(
                this.getMetadata().get(['clientDefs', 'Attachment', 'generalSourceList']) || []
            )
            .filter((item, i, self) => {
                return self.indexOf(item) === i;
            })
            .filter((item) => {
                let defs = sourceDefs[item] || {};

                if (defs.accessDataList) {
                    if (
                        !Espo.Utils.checkAccessDataList(
                            defs.accessDataList, this.getAcl(), this.getUser()
                        )
                    ) {
                        return false;
                    }
                }

                if (defs.configCheck) {
                    let arr = defs.configCheck.split('.');

                    if (!this.getConfig().getByPath(arr)) {
                        return false;
                    }
                }

                return true;
            });

        this.listenTo(this.model, 'change:' + this.nameHashName, () => {
            this.nameHash = _.clone(this.model.get(this.nameHashName)) || {};
        });

        this.on('remove', () => {
            if (this.resizeIsBeingListened) {
                $(window).off('resize.' + this.cid);
            }
        });

        this.on('inline-edit-off', () => {
            this.isUploading = false;
        });
    }

    setupSearch() {
        this.events['change select.search-type'] = e => {
            let type = $(e.currentTarget).val();

            this.handleSearchType(type);
        };
    }

    focusOnInlineEdit() {
        this.focusOnUploadButton();
    }

    focusOnUploadButton() {
        this.$el.find('.attach-file-label').focus();
    }

    empty() {
        this.clearIds();

        this.$attachments.empty();
    }

    handleResize() {
        let width = this.$el.width();

        this.$el.find('img.image-preview').css('maxWidth', width + 'px');
    }

    deleteAttachment(id) {
        this.removeId(id);

        if (this.model.isNew()) {
            this.getModelFactory().create('Attachment', (attachment) => {
                attachment.id = id;
                attachment.destroy();
            });
        }
    }

    getImageUrl(id, size) {
        let url = this.getBasePath() + '?entryPoint=image&id=' + id;

        if (size) {
            url += '&size=' + size;
        }

        if (this.getUser().get('portalId')) {
            url += '&portalId=' + this.getUser().get('portalId');
        }

        return url;
    }

    getDownloadUrl(id) {
        let url = this.getBasePath() + '?entryPoint=download&id=' + id;

        if (this.getUser().get('portalId')) {
            url += '&portalId=' + this.getUser().get('portalId');
        }

        return url;
    }

    removeId(id) {
        let arr = _.clone(this.model.get(this.idsName) || []);
        let i = arr.indexOf(id);

        arr.splice(i, 1);

        this.model.set(this.idsName, arr);

        let nameHash = _.clone(this.model.get(this.nameHashName) || {});
        delete nameHash[id];

        this.model.set(this.nameHashName, nameHash);

        let typeHash = _.clone(this.model.get(this.typeHashName) || {});
        delete typeHash[id];

        this.model.set(this.typeHashName, typeHash);
    }

    clearIds(silent) {
        silent = silent || false;

        this.model.set(this.idsName, [], {silent: silent});
        this.model.set(this.nameHashName, {}, {silent: silent});
        this.model.set(this.typeHashName, {}, {silent: silent})
    }

    pushAttachment(attachment, link, ui) {
        let arr = _.clone(this.model.get(this.idsName) || []);

        arr.push(attachment.id);

        this.model.set(this.idsName, arr, {ui: ui});

        let typeHash = _.clone(this.model.get(this.typeHashName) || {});

        typeHash[attachment.id] = attachment.get('type');

        this.model.set(this.typeHashName, typeHash, {ui: ui});

        let nameHash = _.clone(this.model.get(this.nameHashName) || {});

        nameHash[attachment.id] = attachment.get('name');

        this.model.set(this.nameHashName, nameHash, {ui: ui});
    }

    getEditPreview(name, type, id) {
        if (!~this.previewTypeList.indexOf(type)) {
            return null;
        }

        return  $('<img>')
            .attr('src', this.getImageUrl(id, 'small'))
            .attr('title', name)
            .attr('draggable', false)
            .css({
                maxWidth: (this.imageSizes[this.previewSize] || {})[0],
                maxHeight: (this.imageSizes[this.previewSize] || {})[1],
            })
            .get(0)
            .outerHTML;
    }

    getBoxPreviewHtml(name, type, id) {
        let $text = $('<span>').text(name);

        if (!id) {
            return $text.get(0).outerHTML;
        }

        if (this.showPreviews) {
            let html = this.getEditPreview(name, type, id);

            if (html) {
                return html;
            }
        }

        let url = this.getBasePath() + '?entryPoint=download&id=' + id;

        return $('<a>')
            .attr('href', url)
            .attr('target', '_BLANK')
            .text(name)
            .get(0).outerHTML;
    }

    addAttachmentBox(name, type, id) {
        let $attachments = this.$attachments;

        let $remove = $('<a>')
            .attr('role', 'button')
            .attr('tabindex', '0')
            .addClass('remove-attachment pull-right')
            .append(
                $('<span>').addClass('fas fa-times')
            );

        let previewHtml = this.getBoxPreviewHtml(name, type, id);

        let $att = $('<div>')
            .addClass('gray-box')
            .append($remove)
            .append(
                $('<span>')
                    .addClass('preview')
                    .append(previewHtml)
            );

        let $container = $('<div>').append($att);

        $attachments.append($container);

        if (id) {
            $att.attr('data-id', id);

            return $att;
        }

        let $loading = $('<span>')
            .addClass('small uploading-message')
            .text(this.translate('Uploading...'));

        $container.append($loading);

        $att.on('ready', () => {
            $loading.html(this.translate('Ready'));

            let id = $att.attr('data-id');

            let previewHtml = this.getBoxPreviewHtml(name, type, id);

            $att.find('.preview').html(previewHtml);

            if ($att.find('.preview').find('img').length) {
                $loading.remove();
            }
        });

        return $att;
    }

    showValidationMessage(msg, selector) {
        let $label = this.$el.find('label');
        let title = $label.attr('title');

        $label.attr('title', '');

        super.showValidationMessage(msg, selector);

        $label.attr('title', title);
    }

    getMaxFileSize() {
        let maxFileSize = this.params.maxFileSize || 0;

        let noChunk = !this.getConfig().get('attachmentUploadChunkSize');
        let attachmentUploadMaxSize = this.getConfig().get('attachmentUploadMaxSize') || 0;
        let appMaxUploadSize = this.getHelper().getAppParam('maxUploadSize') || 0;

        if (!maxFileSize || maxFileSize > attachmentUploadMaxSize) {
            maxFileSize = attachmentUploadMaxSize;
        }

        if (noChunk && maxFileSize > appMaxUploadSize) {
            maxFileSize = appMaxUploadSize;
        }

        return maxFileSize;
    }

    uploadFiles(files) {
        let uploadedCount = 0;
        let totalCount = 0;

        let exceedsMaxFileSize = false;

        let maxFileSize = this.getMaxFileSize();

        if (maxFileSize) {
            for (let i = 0; i < files.length; i++) {
                let file = files[i];

                if (file.size > maxFileSize * 1024 * 1024) {
                    exceedsMaxFileSize = true;
                }
            }
        }

        if (exceedsMaxFileSize) {
            let msg = this.translate('fieldMaxFileSizeError', 'messages')
                .replace('{field}', this.getLabelText())
                .replace('{max}', maxFileSize);

            this.showValidationMessage(msg, 'label');

            return;
        }

        this.isUploading = true;

        this.getModelFactory().create('Attachment', model => {
            let canceledList = [];

            let fileList = [];

            for (let i = 0; i < files.length; i++) {
                fileList.push(files[i]);

                totalCount++;
            }

            /** @type module:helpers/file-upload */
            let uploadHelper = new FileUpload(this.getConfig());

            fileList.forEach(file => {
                let $attachmentBox = this.addAttachmentBox(file.name, file.type);

                let $uploadingMsg = $attachmentBox.parent().find('.uploading-message');

                let mediator = {};

                $attachmentBox.find('.remove-attachment').on('click.uploading', () => {
                    canceledList.push(attachment.cid);

                    totalCount--;

                    if (uploadedCount === totalCount) {
                        this.isUploading = false;

                        if (totalCount) {
                            this.afterAttachmentsUploaded.call(this);
                        }
                    }

                    mediator.isCanceled = true;
                });

                let attachment = model.clone();

                attachment.set('role', 'Attachment');
                attachment.set('parentType', this.model.entityType);
                attachment.set('field', this.name);

                uploadHelper
                    .upload(file, attachment, {
                        afterChunkUpload: (size) => {
                            let msg = Math.floor((size / file.size) * 100) + '%';

                            $uploadingMsg.html(msg);
                        },
                        afterAttachmentSave: (attachment) => {
                            $attachmentBox.attr('data-id', attachment.id);
                        },
                        mediator: mediator,
                    })
                    .then(() => {
                        if (canceledList.indexOf(attachment.cid) !== -1) {
                            return;
                        }

                        this.pushAttachment(attachment, null, true);

                        $attachmentBox.attr('data-id', attachment.id);
                        $attachmentBox.trigger('ready');

                        uploadedCount++;

                        if (uploadedCount === totalCount && this.isUploading) {
                            this.model.trigger('attachment-uploaded:' + this.name);
                            this.afterAttachmentsUploaded.call(this);

                            this.isUploading = false;

                            setTimeout(() => {
                                if (
                                    document.activeElement &&
                                    document.activeElement.tagName !== 'BODY'
                                ) {
                                    return;
                                }

                                this.focusOnUploadButton();
                            }, 50);
                        }
                    })
                    .catch(() => {
                        if (mediator.isCanceled) {
                            return;
                        }

                        $attachmentBox.remove();
                        $uploadingMsg.remove();

                        totalCount--;

                        if (!totalCount) {
                            this.isUploading = false;
                        }

                        if (uploadedCount === totalCount && this.isUploading) {
                            this.isUploading = false;
                            this.afterAttachmentsUploaded.call(this);
                        }
                    });
            });
        });
    }

    afterAttachmentsUploaded() {}

    afterRender() {
        if (this.mode === this.MODE_EDIT) {
            this.$attachments = this.$el.find('div.attachments');

            let ids = this.model.get(this.idsName) || [];

            let hameHash = this.model.get(this.nameHashName);
            let typeHash = this.model.get(this.typeHashName) || {};

            ids.forEach(id => {
                if (hameHash) {
                    let name = hameHash[id];
                    let type = typeHash[id] || null;

                    this.addAttachmentBox(name, type, id);
                }
            });

            this.$el.off('drop');
            this.$el.off('dragover');
            this.$el.off('dragleave');

            this.$el.on('drop', e => {
                e.preventDefault();
                e.stopPropagation();

                event = e.originalEvent;

                if (
                    event.dataTransfer &&
                    event.dataTransfer.files &&
                    event.dataTransfer.files.length
                ) {
                    this.uploadFiles(event.dataTransfer.files);
                }
            });

            this.$el.get(0).addEventListener('dragover', e => {
                e.preventDefault();
            });

            this.$el.get(0).addEventListener('dragleave', e => {
                e.preventDefault();
            });
        }

        if (this.mode === this.MODE_SEARCH) {
            let type = this.$el.find('select.search-type').val();

            this.handleSearchType(type);
        }

        if (this.mode === this.MODE_DETAIL) {
            if (this.previewSize === 'large') {
                this.handleResize();
                this.resizeIsBeingListened = true;

                $(window).on('resize.' + this.cid, () => {
                    this.handleResize();
                });
            }
        }
    }

    isTypeIsImage(type) {
        if (~this.previewTypeList.indexOf(type)) {
            return true;
        }

        return false;
    }

    /**
     * @return {string}
     */
    getDetailPreview(name, type, id) {
        if (!this.isTypeIsImage(type)) {
            return $('<span>')
                .text(name)
                .get(0)
                .outerHTML;
        }

        return $('<a>')
            .attr('data-action', 'showImagePreview')
            .attr('data-id', id)
            .attr('title', name)
            .attr('href', this.getImageUrl(id))
            .append(
                $('<img>')
                    .attr('src', this.getImageUrl(id, this.previewSize))
                    .addClass('image-preview')
                    .css({
                        maxWidth: (this.imageSizes[this.previewSize] || {})[0],
                        maxHeight: (this.imageSizes[this.previewSize] || {})[1],
                    })
            )
            .get(0)
            .outerHTML;
    }

    getValueForDisplay() {
        if (this.isDetailMode() || this.isListMode()) {
            let nameHash = this.nameHash;
            let typeHash = this.model.get(this.typeHashName) || {};

            let previews = [];
            let names = [];

            for (let id in nameHash) {
                let type = typeHash[id] || false;
                let name = nameHash[id];

                if (
                    this.showPreviews &&
                    ~this.previewTypeList.indexOf(type) &&
                    (
                        this.isDetailMode() ||
                        this.isListMode() && this.showPreviewsInListMode
                    )
                ) {
                    previews.push(
                        $('<div>')
                            .addClass('attachment-preview')
                            .append(this.getDetailPreview(name, type, id))
                    );

                    continue;
                }

                names.push(
                    $('<div>')
                        .addClass('attachment-block')
                        .append(
                            $('<span>').addClass('fas fa-paperclip text-soft small'),
                            ' ',
                            $('<a>')
                                .attr('href', this.getDownloadUrl(id))
                                .attr('target', '_BLANK')
                                .text(name)
                        )
                );
            }

            let containerClassName = null;

            if (this.previewSize === 'large') {
                containerClassName = 'attachment-block-container-large';
            }

            if (this.previewSize === 'small') {
                containerClassName = 'attachment-block-container-small';
            }

            if (names.length === 0 && previews.length === 0) {
                return '';
            }

            let $container = $('<div>')
                .append(
                    $('<div>')
                        .addClass('attachment-block-container')
                        .addClass(containerClassName)
                        .append(previews)
                )
                .append(names);

            return $container.get(0).innerHTML;
        }
    }

    insertFromSource(source) {
        let viewName =
            this.getMetadata().get(['clientDefs', 'Attachment', 'sourceDefs', source, 'insertModalView']) ||
            this.getMetadata().get(['clientDefs', source, 'modalViews', 'select']) ||
            'views/modals/select-records';

        if (viewName) {
            Espo.Ui.notify(' ... ');

            let filters = null;

            if (('getSelectFilters' + source) in this) {
                filters = this['getSelectFilters' + source]();

                if (this.model.get('parentId') && this.model.get('parentType') === 'Account') {
                    if (
                        this.getMetadata()
                            .get(['entityDefs', source, 'fields', 'account', 'type']) === 'link'
                    ) {
                        filters = {
                            account: {
                                type: 'equals',
                                field: 'accountId',
                                value: this.model.get('parentId'),
                                valueName: this.model.get('parentName')
                            }
                        };
                    }
                }
            }

            let boolFilterList = this.getMetadata()
                .get(['clientDefs', 'Attachment', 'sourceDefs', source, 'boolFilterList']);

            if (('getSelectBoolFilterList' + source) in this) {
                boolFilterList = this['getSelectBoolFilterList' + source]();
            }

            let primaryFilterName = this.getMetadata()
                .get(['clientDefs', 'Attachment', 'sourceDefs', source, 'primaryFilter']);

            if (('getSelectPrimaryFilterName' + source) in this) {
                primaryFilterName = this['getSelectPrimaryFilterName' + source]();
            }

            this.createView('insertFromSource', viewName, {
                scope: source,
                createButton: false,
                filters: filters,
                boolFilterList: boolFilterList,
                primaryFilterName: primaryFilterName,
                multiple: true,
            }, view => {
                view.render();

                Espo.Ui.notify(false);

                this.listenToOnce(view, 'select', (modelList) =>{
                    if (Object.prototype.toString.call(modelList) !== '[object Array]') {
                        modelList = [modelList];
                    }

                    modelList.forEach(model => {
                        if (model.entityType === 'Attachment') {
                            this.pushAttachment(model);

                            return;
                        }

                        Espo.Ajax
                            .postRequest(source + '/action/getAttachmentList', {
                                id: model.id,
                                field: this.name,
                                parentType: this.entityType,
                            })
                            .then(attachmentList => {
                                attachmentList.forEach(item => {
                                    this.getModelFactory().create('Attachment', attachment => {
                                        attachment.set(item);

                                        this.pushAttachment(attachment, true);
                                    });
                                });
                            });
                    });
                });
            });
        }
    }

    validateRequired() {
        if (this.isRequired()) {
            if ((this.model.get(this.idsName) || []).length === 0) {
                let msg = this.translate('fieldIsRequired', 'messages')
                    .replace('{field}', this.getLabelText());

                this.showValidationMessage(msg, 'label');

                return true;
            }
        }
    }

    validateReady() {
        if (this.isUploading) {
            let msg = this.translate('fieldIsUploading', 'messages')
                .replace('{field}', this.getLabelText());

            this.showValidationMessage(msg, 'label');

            return true;
        }
    }

    fetch() {
        let data = {};

        data[this.idsName] = this.model.get(this.idsName) || [];

        return data;
    }

    handleSearchType(type) {
        this.$el.find('div.link-group-container').addClass('hidden');
    }

    fetchSearch() {
        let type = this.$el.find('select.search-type').val();

        if (type === 'isEmpty') {
            return {
                type: 'isNotLinked',
                data: {
                    type: type,
                },
            };
        }

        if (type === 'isNotEmpty') {
            return {
                type: 'isLinked',
                data: {
                    type: type,
                },
            };
        }

        return null;
    }
}

export default AttachmentMultipleFieldView;
PK]!k��||"views/fields/link-category-tree.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import LinkFieldView from 'views/fields/link';

class LinkCategoryTreeFieldView extends LinkFieldView {

    selectRecordsView = 'views/modals/select-category-tree-records'
    autocompleteDisabled = false

    fetchSearch() {
        const data = super.fetchSearch();

        if (!data) {
            return data;
        }

        if (data.typeFront === 'is') {
            data.field = this.name;
            data.type = 'inCategory';
        }

        return data;
    }

    getUrl() {
        const id = this.model.get(this.idName);

        if (!id) {
            return null;
        }

        return '#' + this.entityType + '/list/categoryId=' + id;
    }
}

// noinspection JSUnusedGlobalSymbols
export default LinkCategoryTreeFieldView;
PK]���ǝ
�
views/fields/foreign-enum.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import EnumFieldView from 'views/fields/enum';

class ForeignEnumFieldView extends EnumFieldView {

    type = 'foreign'

    setupOptions() {
        this.params.options = [];

        let field = this.params.field;
        let link = this.params.link;

        if (!field || !link) {
            return;
        }

        let scope = this.getMetadata().get(['entityDefs', this.model.entityType, 'links', link, 'entity']);

        if (!scope) {
            return;
        }

        let {
            optionsPath,
            translation,
            options,
            isSorted,
            displayAsLabel,
            style,
        } = this.getMetadata().get(['entityDefs', scope, 'fields', field]);

        options = optionsPath ? this.getMetadata().get(optionsPath) : options;

        this.params.options = Espo.Utils.clone(options) || [];
        this.params.translation = translation;
        this.params.isSorted = isSorted || false;
        this.params.displayAsLabel = displayAsLabel || false;
        this.styleMap = style || {};

        this.translatedOptions = Object.fromEntries(
            this.params.options
                .map(item => [item, this.getLanguage().translateOption(item, field, scope)])
        );
    }
}

export default ForeignEnumFieldView;
PK]u}r�
�
views/fields/varchar-column.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import VarcharFieldView from 'views/fields/varchar';

class VarcharColumnFieldView extends VarcharFieldView {

    searchTypeList = [
        'startsWith',
        'contains',
        'equals',
        'endsWith',
        'like',
        'isEmpty',
        'isNotEmpty',
    ]

    fetchSearch() {
        const type = this.fetchSearchType() || 'startsWith';

        if (~['isEmpty', 'isNotEmpty'].indexOf(type)) {
            if (type === 'isEmpty') {
                return {
                    typeFront: type,
                    where: {
                        type: 'or',
                        value: [
                            {
                                type: 'columnIsNull',
                                field: this.name,
                            },
                            {
                                type: 'columnEquals',
                                field: this.name,
                                value: '',
                            },
                        ],
                    },
                };
            }

            return  {
                typeFront: type,
                where: {
                    type: 'and',
                    value: [
                        {
                            type: 'columnNotEquals',
                            field: this.name,
                            value: '',
                        },
                        {
                            type: 'columnIsNotNull',
                            field: this.name,
                            value: null,
                        },
                    ],
                },
            };
        }

        let value = this.$element.val().toString().trim();

        value = value.trim();

        if (value) {
            return {
                value: value,
                type: 'column' . Espo.Utils.upperCaseFirst(type),
                data: {
                    type: type,
                    value: value,
                },
            };
        }

        return null;
    }
}

export default VarcharColumnFieldView;

PK]�IE�ii$views/global-search/global-search.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';

class GlobalSearchView extends View {

    template = 'global-search/global-search'

    setup() {
        this.addHandler('keydown', 'input.global-search-input', 'onKeydown');
        this.addHandler('focus', 'input.global-search-input', 'onFocus');
        this.addHandler('click', '[data-action="search"]', () => this.runSearch());

        let promise = this.getCollectionFactory().create('GlobalSearch', collection => {
            this.collection = collection;
            this.collection.url = 'GlobalSearch';
        });

        this.wait(promise);

        this.closeNavbarOnShow = /iPad|iPhone|iPod/.test(navigator.userAgent);
    }

    /**
     * @param {MouseEvent} e
     */
    onFocus(e) {
        let inputElement = /** @type {HTMLInputElement} */e.target;

        inputElement.select();
    }

    /**
     * @param {KeyboardEvent} e
     */
    onKeydown(e) {
        let key = Espo.Utils.getKeyFromKeyEvent(e);

        if (e.code === 'Enter' || key === 'Enter' || key === 'Control+Enter') {
            this.runSearch();

            return;
        }

        if (key === 'Escape') {
            this.closePanel();
        }
    }

    afterRender() {
        this.$input = this.$el.find('input.global-search-input');
    }

    runSearch() {
        let text = this.$input.val().trim();

        if (text !== '' && text.length >= 2) {
            this.search(text);
        }
    }

    search(text) {
        this.collection.url = this.collection.urlRoot = 'GlobalSearch?q=' + encodeURIComponent(text);

        this.showPanel();
    }

    showPanel() {
        this.closePanel();

        if (this.closeNavbarOnShow) {
            this.$el.closest('.navbar-body').removeClass('in');
        }

        let $container = $('<div>').attr('id', 'global-search-panel');

        $container.appendTo(this.$el.find('.global-search-panel-container'));

        this.createView('panel', 'views/global-search/panel', {
            fullSelector: '#global-search-panel',
            collection: this.collection,
        }, view => {
            view.render();

            this.listenToOnce(view, 'close', this.closePanel);
        });

        let $document = $(document);

        $document.on('mouseup.global-search', (e) => {
            if (e.which !== 1) {
                return;
            }

            if (!$container.is(e.target) && $container.has(e.target).length === 0) {
                this.closePanel();
            }
        });

        $document.on('click.global-search', (e) => {
            if (
                e.target.tagName === 'A' &&
                $(e.target).data('action') !== 'showMore' &&
                !$(e.target).hasClass('global-search-button')
            ) {
                setTimeout(() => this.closePanel(), 100);
            }
        });
    }

    closePanel() {
        let $container = $('#global-search-panel');

        $container.remove();

        let $document = $(document);

        if (this.hasView('panel')) {
            this.getView('panel').remove();
        }

        $document.off('mouseup.global-search');
        $document.off('click.global-search');
    }
}

export default GlobalSearchView;
PK]�˕hVV!views/global-search/name-field.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import BaseFieldView from 'views/fields/base';

class GlobalSearchNameFieldView extends BaseFieldView {

    listTemplate = 'global-search/name-field'

    data() {
        return {
            scope: this.model.get('_scope'),
            name: this.model.get('name') || this.translate('None'),
            id: this.model.id,
            iconHtml: this.getHelper().getScopeColorIconHtml(this.model.get('_scope')),
        };
    }
}

export default GlobalSearchNameFieldView;
PK]Њ��"views/global-search/scope-badge.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';

class GlobalSearchScopeBadgeView extends View {

    template = 'global-search/scope-badge'

    data() {
        return {
            label: this.translate(this.model.get('_scope'), 'scopeNames'),
        };
    }
}

export default GlobalSearchScopeBadgeView;
PK]j�.��views/global-search/panel.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';

class GlobalSearchPanel extends View {

    template = 'global-search/panel'

    setup() {
        this.addHandler('click', '[data-action="closePanel"]', () => this.close());

        this.maxSize = this.getConfig().get('globalSearchMaxSize') || 10;

        this.navbarPanelHeightSpace = this.getThemeManager().getParam('navbarPanelHeightSpace') || 100;
        this.navbarPanelBodyMaxHeight = this.getThemeManager().getParam('navbarPanelBodyMaxHeight') || 600;
    }

    onRemove() {
        $(window).off('resize.global-search-height');

        if (this.overflowWasHidden) {
            $('body').css('overflow', 'unset');

            this.overflowWasHidden = false;
        }
    }

    afterRender() {
        this.collection.reset();
        this.collection.maxSize = this.maxSize;

        this.collection.fetch()
            .then(() => this.createRecordView())
            .then(view => view.render());

        const $window = $(window);

        $window.off('resize.global-search-height');
        $window.on('resize.global-search-height', this.processSizing.bind(this));

        this.processSizing();
    }

    /**
     * @return {Promise<module:views/record/list-expanded>}
     */
    createRecordView() {
        return this.createView('list', 'views/record/list-expanded', {
            selector: '.list-container',
            collection: this.collection,
            listLayout: {
                rows: [
                    [
                        {
                            name: 'name',
                            view: 'views/global-search/name-field',
                        }
                    ]
                ],
                right: {
                    name: 'read',
                    view: 'views/global-search/scope-badge',
                    width: '80px',
                },
            }
        });
    }

    processSizing() {
        const $window = $(window);

        let windowHeight = $window.height();
        let windowWidth = $window.width();

        let diffHeight = this.$el.find('.panel-heading').outerHeight();

        let cssParams = {};

        if (windowWidth <= this.getThemeManager().getParam('screenWidthXs')) {
            cssParams.height = (windowHeight - diffHeight) + 'px';
            cssParams.overflow = 'auto';

            $('body').css('overflow', 'hidden');

            this.overflowWasHidden = true;
        }
        else {
            cssParams.height = 'unset';
            cssParams.overflow = 'none';

            if (this.overflowWasHidden) {
                $('body').css('overflow', 'unset');

                this.overflowWasHidden = false;
            }

            if (windowHeight - this.navbarPanelBodyMaxHeight < this.navbarPanelHeightSpace) {
                let maxHeight = windowHeight - this.navbarPanelHeightSpace;

                cssParams.maxHeight = maxHeight + 'px';
            }
        }

        this.$el.find('.panel-body').css(cssParams);
    }

    close() {
        this.trigger('close');
    }
}

export default GlobalSearchPanel;
PK]Y����1views/action-history-record/fields/target-type.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/action-history-record/fields/target-type', ['views/fields/enum'], function (Dep) {

    return Dep.extend({

        setupOptions: function () {
            Dep.prototype.setupOptions.call(this);
            this.params.options = this.getMetadata().getScopeEntityList();
        },
    });
});
PK]��^��	�	,views/action-history-record/fields/target.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/action-history-record/fields/target', ['views/fields/link-parent'], function (Dep) {

    return Dep.extend({

        displayScopeColorInListMode: true,

        ignoreScopeList: ['Preferences', 'ExternalAccount', 'Notification', 'Note', 'ArrayValue'],

        setup: function () {
            Dep.prototype.setup.call(this);

            this.foreignScopeList = this.getMetadata().getScopeEntityList().filter(item => {
                if (!this.getUser().isAdmin()) {
                    if (!this.getAcl().checkScopeHasAcl(item)) {
                        return;
                    }
                }

                if (~this.ignoreScopeList.indexOf(item)) {
                    return;
                }

                if (!this.getAcl().checkScope(item)) {
                    return;
                }

                return true;
            });

            this.getLanguage().sortEntityList(this.foreignScopeList);

            this.foreignScope = this.model.get(this.typeName) || this.foreignScopeList[0];
        },
    });
});
PK]&;ee*views/action-history-record/record/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/action-history-record/record/list', ['views/record/list'], function (Dep) {

    return Dep.extend({

        rowActionsView: 'views/record/row-actions/view-and-remove',

        massActionList: ['remove', 'export'],
    });
});
PK]:��kTT,views/action-history-record/modals/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/action-history-record/modals/detail', ['views/modals/detail'], function (Dep) {

    return Dep.extend({

        fullFormDisabled: true,

        editDisabled: true,

        sideDisabled: true,
    });
});

PK]����77%views/inbound-email/fields/folders.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/inbound-email/fields/folders', ['views/email-account/fields/folders'], function (Dep) {

    return Dep.extend({

        getFoldersUrl: 'InboundEmail/action/getFolders',

    });
});
PK]1>����'views/inbound-email/fields/test-send.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/inbound-email/fields/test-send', ['views/email-account/fields/test-send'], function (Dep) {

    return Dep.extend({

        getSmtpData: function () {
            return {
                'server': this.model.get('smtpHost'),
                'port': this.model.get('smtpPort'),
                'auth': this.model.get('smtpAuth'),
                'security': this.model.get('smtpSecurity'),
                'username': this.model.get('smtpUsername'),
                'password': this.model.get('smtpPassword') || null,
                'authMechanism': this.model.get('smtpAuthMechanism'),
                'fromName': this.model.get('fromName'),
                'fromAddress': this.model.get('emailAddress'),
                'type': 'inboundEmail',
                'id': this.model.id,
            };
        },
     });
});
PK]>U~��2views/inbound-email/fields/target-user-position.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/inbound-email/fields/target-user-position', ['views/fields/enum'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            this.translatedOptions = {
                '': '--' + this.translate('All') + '--'
            };

            this.params.options = [''];

            if (this.model.get('targetUserPosition') && this.model.get('teamId')) {
                this.params.options.push(this.model.get('targetUserPosition'));
            }

            this.loadRoleList(() => {
                if (this.mode === 'edit') {
                    if (this.isRendered()) {
                        this.render();
                    }
                }
            });

            this.listenTo(this.model, 'change:teamId', () => {
                this.loadRoleList(() => {
                    this.render();
                });
            });
        },

        loadRoleList: function (callback, context) {
            var teamId = this.model.get('teamId');

            if (!teamId) {
                this.params.options = [''];
            }

            this.getModelFactory().create('Team', (team) => {
                team.id = teamId;

                this.listenToOnce(team, 'sync', () => {
                    this.params.options = team.get('positionList') || [];
                    this.params.options.unshift('');

                    callback.call(context);
                });

                team.fetch();
            });
        },
    });
});
PK]M��f��+views/inbound-email/fields/email-address.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/inbound-email/fields/email-address', ['views/fields/email-address'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            this.on('change', () => {
                var emailAddress = this.model.get('emailAddress');

                this.model.set('name', emailAddress);

                if (this.model.isNew() || !this.model.get('replyToAddress')) {
                    this.model.set('replyToAddress', emailAddress);
                }
            });
        },
    });
});
PK]�� �@@-views/inbound-email/fields/test-connection.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/inbound-email/fields/test-connection', ['views/email-account/fields/test-connection'], function (Dep) {

    return Dep.extend({

        url: 'InboundEmail/action/testConnection',
     });
});
PK]S��[55$views/inbound-email/fields/folder.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/inbound-email/fields/folder', ['views/email-account/fields/folder'], function (Dep) {

    return Dep.extend({

        getFoldersUrl: 'InboundEmail/action/getFolders',

    });
});
PK]H�a߷
�
"views/inbound-email/record/edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/inbound-email/record/edit', ['views/record/edit', 'views/inbound-email/record/detail'],
function (Dep, Detail) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            Detail.prototype.setupFieldsBehaviour.call(this);
            Detail.prototype.initSslFieldListening.call(this);

            if (Detail.prototype.wasFetched.call(this)) {
                this.setFieldReadOnly('fetchSince');
            }
        },

        modifyDetailLayout: function (layout) {
            Detail.prototype.modifyDetailLayout.call(this, layout);
        },

        controlStatusField: function () {
            Detail.prototype.controlStatusField.call(this);
        },

        initSmtpFieldsControl: function () {
            Detail.prototype.initSmtpFieldsControl.call(this);
        },

        controlSmtpFields: function () {
            Detail.prototype.controlSmtpFields.call(this);
        },

        controlSentFolderField: function () {
            Detail.prototype.controlSentFolderField.call(this);
        },

        controlSmtpAuthField: function () {
            Detail.prototype.controlSmtpAuthField.call(this);
        },

        wasFetched: function () {
            Detail.prototype.wasFetched.call(this);
        },
    });
});
PK]r%��(�($views/inbound-email/record/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/inbound-email/record/detail', ['views/record/detail'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            this.setupFieldsBehaviour();
            this.initSslFieldListening();
        },

        modifyDetailLayout: function (layout) {
            layout.filter(panel => panel.tabLabel === '$label:SMTP').forEach(panel => {
                panel.rows.forEach(row => {
                    row.forEach(item => {
                        let labelText = this.translate(item.name, 'fields', 'InboundEmail');

                        if (labelText && labelText.indexOf('SMTP ') === 0) {
                            item.labelText = Espo.Utils.upperCaseFirst(labelText.substring(5));
                        }
                    });
                })
            });
        },

        wasFetched: function () {
            if (!this.model.isNew()) {
                return !!((this.model.get('fetchData') || {}).lastUID);
            }

            return false;
        },

        initSmtpFieldsControl: function () {
            this.controlSmtpFields();
            this.controlSentFolderField();
            this.listenTo(this.model, 'change:useSmtp', this.controlSmtpFields, this);
            this.listenTo(this.model, 'change:smtpAuth', this.controlSmtpFields, this);
            this.listenTo(this.model, 'change:storeSentEmails', this.controlSentFolderField, this);
        },

        controlSmtpFields: function () {
            if (this.model.get('useSmtp')) {
                this.showField('smtpHost');
                this.showField('smtpPort');
                this.showField('smtpAuth');
                this.showField('smtpSecurity');
                this.showField('smtpTestSend');
                this.showField('fromName');
                this.showField('smtpIsShared');
                this.showField('smtpIsForMassEmail');
                this.showField('storeSentEmails');

                this.setFieldRequired('smtpHost');
                this.setFieldRequired('smtpPort');

                this.controlSmtpAuthField();

                return;
            }

            this.hideField('smtpHost');
            this.hideField('smtpPort');
            this.hideField('smtpAuth');
            this.hideField('smtpUsername');
            this.hideField('smtpPassword');
            this.hideField('smtpAuthMechanism');
            this.hideField('smtpSecurity');
            this.hideField('smtpTestSend');
            this.hideField('fromName');
            this.hideField('smtpIsShared');
            this.hideField('smtpIsForMassEmail');
            this.hideField('storeSentEmails');
            this.hideField('sentFolder');

            this.setFieldNotRequired('smtpHost');
            this.setFieldNotRequired('smtpPort');
            this.setFieldNotRequired('smtpUsername');
        },

        controlSentFolderField: function () {
            if (this.model.get('useSmtp') && this.model.get('storeSentEmails')) {
                this.showField('sentFolder');
                this.setFieldRequired('sentFolder');

                return;
            }

            this.hideField('sentFolder');
            this.setFieldNotRequired('sentFolder');
        },

        controlSmtpAuthField: function () {
            if (this.model.get('smtpAuth')) {
                this.showField('smtpUsername');
                this.showField('smtpPassword');
                this.showField('smtpAuthMechanism');
                this.setFieldRequired('smtpUsername');

                return;
            }

            this.hideField('smtpUsername');
            this.hideField('smtpPassword');
            this.hideField('smtpAuthMechanism');
            this.setFieldNotRequired('smtpUsername');
        },

        controlStatusField: function () {
            let list = ['username', 'port', 'host', 'monitoredFolders'];

            if (this.model.get('status') === 'Active' && this.model.get('useImap')) {
                list.forEach(item => {
                    this.setFieldRequired(item);
                });

                return;
            }

            list.forEach(item => {
                this.setFieldNotRequired(item);
            });
        },

        setupFieldsBehaviour: function () {
            this.controlStatusField();

            this.listenTo(this.model, 'change:status', (model, value, o) => {
                if (o.ui) {
                    this.controlStatusField();
                }
            });

            this.listenTo(this.model, 'change:useImap', (model, value, o) => {
                if (o.ui) {
                    this.controlStatusField();
                }
            });

            if (this.wasFetched()) {
                this.setFieldReadOnly('fetchSince');
            } else {
                this.setFieldNotReadOnly('fetchSince');
            }

            this.initSmtpFieldsControl();

            let handleRequirement = (model) => {
                if (model.get('createCase')) {
                    this.showField('caseDistribution');
                } else {
                    this.hideField('caseDistribution');
                }

                if (
                    model.get('createCase') &&
                    ['Round-Robin', 'Least-Busy'].indexOf(model.get('caseDistribution')) !== -1
                ) {
                    this.setFieldRequired('team');
                    this.showField('targetUserPosition');
                } else {
                    this.setFieldNotRequired('team');
                    this.hideField('targetUserPosition');
                }

                if (model.get('createCase') && 'Direct-Assignment' === model.get('caseDistribution')) {
                    this.setFieldRequired('assignToUser');
                    this.showField('assignToUser');
                } else {
                    this.setFieldNotRequired('assignToUser');
                    this.hideField('assignToUser');
                }

                if (model.get('createCase') && model.get('createCase') !== '') {
                    this.showField('team');
                } else {
                    this.hideField('team');
                }
            };

            this.listenTo(this.model, 'change:createCase', (model, value, o) => {
                handleRequirement(model);

                if (!o.ui) {
                    return;
                }

                if (!model.get('createCase')) {
                    this.model.set({
                        caseDistribution: '',
                        teamId: null,
                        teamName: null,
                        assignToUserId: null,
                        assignToUserName: null,
                        targetUserPosition: '',
                    });
                }
            });

            handleRequirement(this.model);

            this.listenTo(this.model, 'change:caseDistribution', (model, value, o) => {
                handleRequirement(model);

                if (!o.ui) {
                    return;
                }

                setTimeout(() => {
                    if (!this.model.get('caseDistribution')) {
                        this.model.set({
                            assignToUserId: null,
                            assignToUserName: null,
                            targetUserPosition: ''
                        });

                        return;
                    }

                    if (this.model.get('caseDistribution') === 'Direct-Assignment') {
                        this.model.set({
                            targetUserPosition: '',
                        });
                    }

                    this.model.set({
                        assignToUserId: null,
                        assignToUserName: null,
                    });
                }, 10);
            });
        },

        initSslFieldListening: function () {
            this.listenTo(this.model, 'change:security', (model, value, o) => {
                if (!o.ui) {
                    return;
                }

                if (value) {
                    this.model.set('port', 993);
                } else {
                    this.model.set('port', 143);
                }
            });

            this.listenTo(this.model, 'change:smtpSecurity', (model, value, o) => {
                if (!o.ui) {
                    return;
                }

                if (value === 'SSL') {
                    this.model.set('smtpPort', 465);
                } else if (value === 'TLS') {
                    this.model.set('smtpPort', 587);
                } else {
                    this.model.set('smtpPort', 25);
                }
            });
        },
    });
});
PK]w	���"views/inbound-email/record/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/inbound-email/record/list', ['views/record/list'], function (Dep) {

    return Dep.extend({

    	quickDetailDisabled: true,
        quickEditDisabled: true,
        massActionList: ['remove', 'massUpdate'],
        checkAllResultDisabled: true,
    });
});
PK]O����views/portal-user/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/portal-user/list', ['views/list'], function (Dep) {

    return Dep.extend({

        defaultOrderBy: 'createdAt',

        defaultOrder: 'desc',

        setup: function () {
            Dep.prototype.setup.call(this);
        },

        actionCreate: function () {
            var viewName = 'crm:views/contact/modals/select-for-portal-user';

            this.createView('modal', viewName, {
                scope: 'Contact',
                primaryFilterName: 'notPortalUsers',
                createButton: false,
                mandatorySelectAttributeList: [
                    'salutationName',
                    'firstName',
                    'lastName',
                    'accountName',
                    'accountId',
                    'emailAddress',
                    'emailAddressData',
                    'phoneNumber',
                    'phoneNumberData',
                ]
            }, view => {
                view.render();

                this.listenToOnce(view, 'select', model => {
                    var attributes = {};

                    attributes.contactId = model.id;
                    attributes.contactName = model.get('name');

                    if (model.get('accountId')) {
                        var names = {};
                        names[model.get('accountId')] = model.get('accountName');

                        attributes.accountsIds = [model.get('accountId')];
                        attributes.accountsNames = names;
                    }

                    attributes.firstName = model.get('firstName');
                    attributes.lastName = model.get('lastName');
                    attributes.salutationName = model.get('salutationName');

                    attributes.emailAddress = model.get('emailAddress');
                    attributes.emailAddressData = model.get('emailAddressData');

                    attributes.phoneNumber = model.get('phoneNumber');
                    attributes.phoneNumberData = model.get('phoneNumberData');

                    attributes.userName = attributes.emailAddress;

                    attributes.type = 'portal';

                    var router = this.getRouter();

                    var url = '#' + this.scope + '/create';

                    router.dispatch(this.scope, 'create', {
                        attributes: attributes
                    });

                    router.navigate(url, {trigger: false});
                });

                this.listenToOnce(view, 'skip', () => {
                    var attributes = {
                        type: 'portal',
                    };

                    var router = this.getRouter();
                    var url = '#' + this.scope + '/create';

                    router.dispatch(this.scope, 'create', {
                        attributes: attributes
                    });

                    router.navigate(url, {trigger: false});
                });
            });
        },
    });
});
PK]�F�55.views/lead-capture-log-record/modals/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/lead-capture-log-record/modals/detail', ['views/modals/detail'], function (Dep) {

    return Dep.extend({

       editDisabled: true,

       fullFormDisabled: true,

    });
});
PK]��>�LL"views/email/fields/create-event.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email/fields/create-event', ['views/fields/base'], function (Dep) {

    return Dep.extend({

        detailTemplate: 'email/fields/create-event/detail',

        eventEntityType: 'Meeting',

        getAttributeList: function () {
            return [
                'icsEventData',
                'createdEventId',
            ];
        },

        events: {
            'click [data-action="createEvent"]': function () {
                this.createEvent();
            },
        },

        createEvent: function () {
            let viewName = this.getMetadata().get(['clientDefs', this.eventEntityType, 'modalViews', 'edit']) ||
                'views/modals/edit';

            let eventData = this.model.get('icsEventData') || {};

            let attributes = Espo.Utils.cloneDeep(eventData.valueMap || {});

            attributes.parentId = this.model.get('parentId');
            attributes.parentType = this.model.get('parentType');
            attributes.parentName = this.model.get('parentName');

            this.addFromAddressToAttributes(attributes);

            this.createView('dialog', viewName, {
                attributes: attributes,
                scope: this.eventEntityType,
            })
                .then(view => {
                    view.render();

                    this.listenToOnce(view, 'after:save', () => {
                        this.model
                            .fetch()
                            .then(() =>
                                Espo.Ui.success(this.translate('Done'))
                            );
                    });
                });
        },

        addFromAddressToAttributes: function (attributes) {
            let fromAddress = this.model.get('from');
            let idHash = this.model.get('idHash') || {};
            let typeHash = this.model.get('typeHash') || {};
            let nameHash = this.model.get('nameHash') || {};

            let fromId = null;
            let fromType = null;
            let fromName = null;

            if (!fromAddress) {
                return;
            }

            fromId = idHash[fromAddress] || null;
            fromType = typeHash[fromAddress] || null;
            fromName = nameHash[fromAddress] || null;

            let attendeeLink = this.getAttendeeLink(fromType);

            if (!attendeeLink) {
                return;
            }

            attributes[attendeeLink + 'Ids'] = attributes[attendeeLink + 'Ids'] || [];
            attributes[attendeeLink + 'Names'] = attributes[attendeeLink + 'Names'] || {};

            if (~attributes[attendeeLink + 'Ids'].indexOf(fromId)) {
                return;
            }

            attributes[attendeeLink + 'Ids'].push(fromId);
            attributes[attendeeLink + 'Names'][fromId] = fromName;
        },

        getAttendeeLink: function (entityType) {
            if (entityType === 'User') {
                return 'users';
            }

            if (entityType === 'Contact') {
                return 'contacts';
            }

            if (entityType === 'Lead') {
                return 'leads';
            }

            return null;
        },

    });
});
PK]�)����views/email/fields/body.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email/fields/body', ['views/fields/wysiwyg'], function (Dep) {

    return Dep.extend({

        useIframe: true,

        getAttributeList: function () {
            return ['body', 'bodyPlain'];
        },

        setupToolbar: function () {
            Dep.prototype.setupToolbar.call(this);

            this.toolbar.unshift([
                'insert-field',
                ['insert-field']
            ]);

            this.buttons['insert-field'] = function (context) {
                var ui = $.summernote.ui;
                var button = ui.button({
                    contents: '<i class="fas fa-plus"></i>',
                    tooltip: this.translate('Insert Field', 'labels', 'Email'),
                    click: function () {
                        this.showInsertFieldModal();
                    }.bind(this)
                });
                return button.render();
            }.bind(this);

            this.listenTo(this.model, 'change', function (m) {
                if (!this.isRendered()) return;
                if (m.hasChanged('parentId') || m.hasChanged('to')) {
                    this.controInsertFieldButton();
                }
            }, this);
        },

        afterRender: function () {
            Dep.prototype.afterRender.call(this);

            this.controInsertFieldButton();
        },

        controInsertFieldButton: function () {
            var $b = this.$el.find('.note-insert-field > button');

            if (this.model.get('to') && this.model.get('to').length || this.model.get('parentId')) {
                $b.removeAttr('disabled').removeClass('disabled');
            } else {
                $b.attr('disabled', 'disabled').addClass('disabled');
            }
        },

        showInsertFieldModal: function () {
            var to = this.model.get('to');
            if (to) {
                to = to.split(';')[0].trim();
            }
            var parentId = this.model.get('parentId');
            var parentType = this.model.get('parentType');

            Espo.Ui.notify(' ... ');

            this.createView('insertFieldDialog', 'views/email/modals/insert-field', {
                parentId: parentId,
                parentType: parentType,
                to: to,
            }, function (view) {
                view.render();
                Espo.Ui.notify();

                this.listenToOnce(view, 'insert', function (string) {
                    if (this.$summernote) {
                        if (~string.indexOf('\n')) {
                            string = string.replace(/(?:\r\n|\r|\n)/g, '<br>');
                            var html = '<p>' + string + '</p>';
                            this.$summernote.summernote('editor.pasteHTML', html);
                        } else {
                            this.$summernote.summernote('editor.insertText', string);
                        }
                    }
                    this.clearView('insertFieldDialog');
                }, this);
            });
        },

    });
});
PK]�v	��views/email/fields/subject.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email/fields/subject', ['views/fields/varchar'], function (Dep) {

    return Dep.extend({

        listLinkTemplate: 'email/fields/subject/list-link',

        data: function () {
            let data = Dep.prototype.data.call(this);

            data.isRead = (this.model.get('sentById') === this.getUser().id) || this.model.get('isRead');
            data.isImportant = this.model.has('isImportant') && this.model.get('isImportant');
            data.hasAttachment = this.model.has('hasAttachment') && this.model.get('hasAttachment');
            data.isReplied = this.model.has('isReplied') && this.model.get('isReplied');
            data.inTrash = this.model.has('inTrash') && this.model.get('inTrash');

            if (!data.isRead && !this.model.has('isRead')) {
                data.isRead = true;
            }

            if (!data.isNotEmpty) {
                if (
                    this.model.get('name') !== null &&
                    this.model.get('name') !== '' &&
                    this.model.has('name')
                ) {
                    data.isNotEmpty = true;
                }
            }

            return data;
        },

        getValueForDisplay: function () {
            return this.model.get('name');
        },

        getAttributeList: function () {
            return ['name', 'subject', 'isRead', 'isImportant', 'hasAttachment', 'inTrash'];
        },

        setup: function () {
            Dep.prototype.setup.call(this);

            this.events['click [data-action="showAttachments"]'] = e => {
                e.stopPropagation();

                this.showAttachments();
            }

            this.listenTo(this.model, 'change', () => {
                if (this.mode === 'list' || this.mode === 'listLink') {
                    if (this.model.hasChanged('isRead') || this.model.hasChanged('isImportant')) {
                        this.reRender();
                    }
                }
            });
        },

        afterRender: function () {
            Dep.prototype.afterRender.call(this);
        },

        fetch: function () {
            var data = Dep.prototype.fetch.call(this);
            data.name = data.subject;
            return data;
        },

        showAttachments: function () {
            Espo.Ui.notify(' ... ');

            this.createView('dialog', 'views/email/modals/attachments', {model: this.model})
                .then(view => {
                    view.render();

                    Espo.Ui.notify(false);
                });
        },
    });
});
PK].;�*	*	views/email/fields/replies.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email/fields/replies', ['views/fields/link-multiple'], function (Dep) {

    return Dep.extend({

        getAttributeList: function () {
            let attributeList = Dep.prototype.getAttributeList.call(this);

            attributeList.push(this.name + 'Columns');

            return attributeList;
        },

        getDetailLinkHtml: function (id) {
            let html = Dep.prototype.getDetailLinkHtml.call(this, id);

            let columns = this.model.get(this.name + 'Columns') || {};

            let status = (columns[id] || {})['status'];

            return $('<div>')
                .append(
                    $('<span>')
                        .addClass('fas fa-arrow-right fa-sm link-multiple-item-icon')
                        .addClass(status === 'Draft' ? 'text-warning' : 'text-success')
                )
                .append(html)
                .html();
        },
    });
});
PK]���ss(views/email/fields/from-email-address.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email/fields/from-email-address', ['views/fields/link'], function (Dep) {

    return Dep.extend({

        listTemplate: 'email/fields/from-email-address/detail',

        detailTemplate: 'email/fields/from-email-address/detail',
    });
});
PK]�Q��IIviews/email/fields/icon.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email/fields/icon', ['views/fields/base'], function (Dep) {

    return Dep.extend({

        listTemplate: 'email/fields/icon/detail',

        detailTemplate: 'email/fields/icon/detail',
    });
});
PK]a0��\\5views/email/fields/person-string-data-for-expanded.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email/fields/person-string-data-for-expanded', ['views/email/fields/person-string-data'], function (Dep) {

    return Dep.extend({

        listTemplate: 'email/fields/person-string-data/list-for-expanded',

    });
});
PK]��$�XX(views/email/fields/person-string-data.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email/fields/person-string-data', ['views/fields/varchar'], function (Dep) {

    return Dep.extend({

        listTemplate: 'email/fields/person-string-data/list',

        getAttributeList: function () {
            return ['personStringData', 'isReplied'];
        },

        data: function () {
            var data = Dep.prototype.data.call(this);

            data.isReplied = this.model.get('isReplied');

            return data;
        },
    });
});
PK]�?o��#views/email/fields/email-address.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email/fields/email-address', ['views/fields/base'], function (Dep) {

    return Dep.extend({

        getAutocompleteMaxCount: function () {
            if (this.autocompleteMaxCount) {
                return this.autocompleteMaxCount;
            }

            return this.getConfig().get('recordsPerPage');
        },

        afterRender: function () {
            Dep.prototype.afterRender.call(this);

            this.$input = this.$el.find('input');

            if (this.mode === this.MODE_SEARCH && this.getAcl().check('Email', 'create')) {
                this.initSearchAutocomplete();
            }

            if (this.mode === this.MODE_SEARCH) {
                this.$input.on('input', () => {
                    this.trigger('change');
                });
            }
        },

        initSearchAutocomplete: function () {
            this.$input = this.$input || this.$el.find('input');

            this.$input.autocomplete({
                serviceUrl: () => {
                    return `EmailAddress/search` +
                        `?maxSize=${this.getAutocompleteMaxCount()}`
                },
                paramName: 'q',
                minChars: 1,
                autoSelectFirst: true,
                triggerSelectOnValidInput: false,
                noCache: true,
                formatResult: suggestion => {
                    return this.getHelper().escapeString(suggestion.name) + ' &#60;' +
                        this.getHelper().escapeString(suggestion.id) + '&#62;';
                },
                transformResult: response => {
                    response = JSON.parse(response);

                    let list = response.map(item => {
                        return {
                            id: item.emailAddress,
                            name: item.entityName,
                            emailAddress: item.emailAddress,
                            entityId: item.entityId,
                            entityName: item.entityName,
                            entityType: item.entityType,
                            data: item.emailAddress,
                            value: item.emailAddress,
                        }
                    });

                    if (this.skipCurrentInAutocomplete) {
                        let current = this.$input.val();

                        list = list.filter(item => item.emailAddress !== current)
                    }

                    return {suggestions: list};
                },
                onSelect: (s) => {
                    this.$input.val(s.emailAddress);
                    this.$input.focus();
                },
            });

            this.once('render', () => {
                this.$input.autocomplete('dispose');
            });

            this.once('remove', () => {
                this.$input.autocomplete('dispose');
            });
        },

        fetchSearch: function () {
            let value = this.$element.val();

            if (typeof value.trim === 'function') {
                value = value.trim();
            }

            if (value) {
                return {
                    type: 'equals',
                    value: value,
                };
            }

            return null;
        },
    });
});
PK]s��*��#views/email/fields/created-event.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email/fields/created-event', ['views/fields/link-parent'], function (Dep) {

    return class extends Dep {

        data() {
            let data = super.data();

            let icsEventData = this.model.get('icsEventData') || {};

            if (
                this.isReadMode() &&
                !data.idValue &&
                icsEventData.createdEvent
            ) {
                data.idValue = icsEventData.createdEvent.id;
                data.typeValue = icsEventData.createdEvent.entityType;
                data.nameValue = icsEventData.createdEvent.name;
            }

            return data;
        }

        getAttributeList() {
            let list = super.getAttributeList();

            list.push('icsEventData');

            return list;
        }
    };
});
PK]��h��>�>+views/email/fields/email-address-varchar.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email/fields/email-address-varchar',
['views/fields/base', 'views/email/fields/from-address-varchar', 'views/email/fields/email-address'],
function (Dep, From, EmailAddress) {

    return Dep.extend({

        detailTemplate: 'email/fields/email-address-varchar/detail',
        editTemplate: 'email/fields/email-address-varchar/edit',

        emailAddressRegExp: new RegExp(
            /^[-!#$%&'*+/=?^_`{|}~A-Za-z0-9]+(?:\.[-!#$%&'*+/=?^_`{|}~A-Za-z0-9]+)*/.source +
            /@([A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?\.)+[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9]/.source
        ),

        data: function () {
            let data = Dep.prototype.data.call(this);

            data.valueIsSet = this.model.has(this.name);
            data.maxLength = 254;

            return data;
        },

        events: {
            'click a[data-action="clearAddress"]': function (e) {
                let address = $(e.currentTarget).data('address').toString();

                this.deleteAddress(address);
            },
            'keyup input': function (e) {
                if (!this.isEditMode()) {
                    return;
                }

                let key = Espo.Utils.getKeyFromKeyEvent(e);

                if (
                    key === 'Comma' ||
                    key === 'Semicolon' ||
                    key === 'Enter'
                ) {
                    let $input = $(e.currentTarget);
                    let address = $input.val().replace(',', '').replace(';', '').trim();

                    if (address.indexOf('@') === -1) {
                        return;
                    }

                    if (this.checkEmailAddressInString(address)) {
                        this.addAddress(address, '');
                        $input.val('');
                    }
                }
            },
            'change input': function (e) {
                if (!this.isEditMode()) {
                    return;
                }

                let $input = $(e.currentTarget);
                let address = $input.val().replace(',','').replace(';','').trim();

                if (address.indexOf('@') === -1) {
                    return;
                }

                if (this.checkEmailAddressInString(address)) {
                    this.addAddress(address, '');

                    $input.val('');
                }
            },
            'click [data-action="createContact"]': function (e) {
                let address = $(e.currentTarget).data('address');

                From.prototype.createPerson.call(this, 'Contact', address);
            },
            'click [data-action="createLead"]': function (e) {
                let address = $(e.currentTarget).data('address');

                From.prototype.createPerson.call(this, 'Lead', address);
            },
            'click [data-action="addToContact"]': function (e) {
                let address = $(e.currentTarget).data('address');

                From.prototype.addToPerson.call(this, 'Contact', address);
            },
            'click [data-action="addToLead"]': function (e) {
                let address = $(e.currentTarget).data('address');

                From.prototype.addToPerson.call(this, 'Lead', address);
            },
            'auxclick a[href][data-scope][data-id]': function (e) {
                let isCombination = e.button === 1 && (e.ctrlKey || e.metaKey);

                if (!isCombination) {
                    return;
                }

                let $target = $(e.currentTarget);

                let id = $target.attr('data-id');
                let scope = $target.attr('data-scope');

                e.preventDefault();
                e.stopPropagation();

                From.prototype.quickView.call(this, {
                    id: id,
                    scope: scope,
                });
            },
        },

        getAutocompleteMaxCount: function () {
            if (this.autocompleteMaxCount) {
                return this.autocompleteMaxCount;
            }

            return this.getConfig().get('recordsPerPage');
        },

        parseNameFromStringAddress: function (s) {
            return From.prototype.parseNameFromStringAddress.call(this, s);
        },

        getAttributeList: function () {
            var list = Dep.prototype.getAttributeList.call(this);

            list.push('nameHash');
            list.push('typeHash');
            list.push('idHash');
            list.push('accountId');
            list.push(this.name + 'EmailAddressesNames');
            list.push(this.name + 'EmailAddressesIds');

            return list;
        },

        setup: function () {
            Dep.prototype.setup.call(this);

            this.on('render', () => {
                this.initAddressList();
            });
        },

        initAddressList: function () {
            this.nameHash = {};

            this.addressList = (this.model.get(this.name) || '')
                .split(';')
                .filter((item) => {
                    return item !== '';
                })
                .map(item => {
                    return item.trim();
                });

            this.idHash = this.idHash || {};
            this.typeHash = this.typeHash || {};
            this.nameHash = this.nameHash || {};

            _.extend(this.typeHash, this.model.get('typeHash') || {});
            _.extend(this.nameHash, this.model.get('nameHash') || {});
            _.extend(this.idHash, this.model.get('idHash') || {});

            this.nameHash = _.clone(this.nameHash);
            this.typeHash = _.clone(this.typeHash);
            this.idHash = _.clone(this.idHash);
        },

        afterRender: function () {
            Dep.prototype.afterRender.call(this);

            if (this.isEditMode()) {
                this.$input = this.$element = this.$el.find('input');

                this.addressList.forEach(item => {
                    this.addAddressHtml(item, this.nameHash[item] || '');
                });

                this.$input.autocomplete({
                    serviceUrl: () => {
                        return `EmailAddress/search` +
                            `?maxSize=${this.getAutocompleteMaxCount()}` +
                            `&onlyActual=true`;
                    },
                    paramName: 'q',
                    minChars: 1,
                    autoSelectFirst: true,
                    noCache: true,
                    triggerSelectOnValidInput: false,
                    formatResult: (suggestion) => {
                        return this.getHelper().escapeString(suggestion.name) + ' &#60;' +
                            this.getHelper().escapeString(suggestion.id) + '&#62;';
                    },
                    transformResult: (response) => {
                        response = JSON.parse(response);
                        var list = [];

                        response.forEach((item) => {
                            list.push({
                                id: item.emailAddress,
                                name: item.entityName,
                                emailAddress: item.emailAddress,
                                entityId: item.entityId,
                                entityName: item.entityName,
                                entityType: item.entityType,
                                data: item.emailAddress,
                                value: item.emailAddress,
                            });
                        });

                        return {
                            suggestions: list
                        };
                    },
                    onSelect: (s) => {
                        this.addAddress(s.emailAddress, s.entityName, s.entityType, s.entityId);

                        this.$input.val('');
                        this.$input.focus();
                    },
                });

                this.once('render', () => {
                    this.$input.autocomplete('dispose');
                });

                this.once('remove', () => {
                    this.$input.autocomplete('dispose');
                });
            }

            if (this.mode === 'search' && this.getAcl().check('Email', 'create')) {
                EmailAddress.prototype.initSearchAutocomplete.call(this);
            }

            if (this.mode === 'search') {
                this.$input.on('input', () => {
                    this.trigger('change');
                });
            }
        },

        checkEmailAddressInString: function (string) {
            var arr = string.match(this.emailAddressRegExp);

            if (!arr || !arr.length) {
                return;
            }

            return true;
        },

        addAddress: function (address, name, type, id) {
            if (this.justAddedAddress) {
                this.deleteAddress(this.justAddedAddress);
            }

            this.justAddedAddress = address;

            setTimeout(() => {
                this.justAddedAddress = null;
            }, 100);

            address = address.trim();

            if (!type) {
                var arr = address.match(this.emailAddressRegExp);

                if (!arr || !arr.length) {
                    return;
                }

                address = arr[0];
            }

            if (!~this.addressList.indexOf(address)) {
                this.addressList.push(address);
                this.nameHash[address] = name;

                if (type) {
                    this.typeHash[address] = type;
                }

                if (id) {
                    this.idHash[address] = id;
                }

                this.addAddressHtml(address, name);
                this.trigger('change');
            }
        },

        addAddressHtml: function (address, name) {
            let $container = this.$el.find('.link-container');

            let $text = $('<span>');

            if (name) {
                $text.append(
                    $('<span>').text(name),
                    ' ',
                    $('<span>').addClass('text-muted chevron-right'),
                    ' '
                );
            }

            $text.append(
                $('<span>').text(address)
            );

            let $div = $('<div>')
                .attr('data-address', address)
                .addClass('list-group-item')
                .append(
                    $('<a>')
                        .attr('data-address', address)
                        .attr('role', 'button')
                        .attr('tabindex', '0')
                        .attr('data-action', 'clearAddress')
                        .addClass('pull-right')
                        .append(
                            $('<span>').addClass('fas fa-times')
                        ),
                    $text
                );

            $container.append($div);
        },

        deleteAddress: function (address) {
            this.deleteAddressHtml(address);

            var index = this.addressList.indexOf(address);

            if (index > -1) {
                this.addressList.splice(index, 1);
            }

            delete this.nameHash[address];

            this.trigger('change');
        },

        deleteAddressHtml: function (address) {
            this.$el.find('.list-group-item[data-address="' + address + '"]').remove();
        },

        fetch: function () {
            let data = {};

            data[this.name] = this.addressList.join(';');

            return data;
        },

        fetchSearch: function () {
            let value = this.$element.val().trim();

            if (value) {
                return {
                    type: 'equals',
                    value: value,
                };
            }

            return null;
        },

        getValueForDisplay: function () {
            if (this.isDetailMode()) {
                let names = [];

                this.addressList.forEach((address) => {
                    names.push(this.getDetailAddressHtml(address));
                });

                return names.join('');
            }
        },

        getDetailAddressHtml: function (address) {
            if (!address) {
                return '';
            }

            let name = this.nameHash[address] || null;
            let entityType = this.typeHash[address] || null;
            let id = this.idHash[address] || null;

            if (id) {
                return $('<div>')
                    .append(
                        $('<a>')
                            .attr('href', '#' + entityType + '/view/' + id)
                            .attr('data-scope', entityType)
                            .attr('data-id', id)
                            .text(name),
                        ' <span class="text-muted chevron-right"></span> ',
                        $('<span>').text(address)
                    )
                    .get(0).outerHTML;
            }

            let $div = $('<div>');

            if (name) {
                $div.append(
                    $('<span>')
                        .addClass('email-address-line')
                        .text(name)
                        .append(' <span class="text-muted chevron-right"></span> ')
                        .append(
                            $('<span>').text(address)
                        )
                );
            }
            else {
                $div.append(
                    $('<span>')
                        .addClass('email-address-line')
                        .text(address)
                );
            }

            if (this.getAcl().check('Contact', 'create') || this.getAcl().check('Lead', 'create')) {
                $div.prepend(
                    From.prototype.getCreateHtml.call(this, address)
                );
            }

            return $div.get(0).outerHTML;
        },

        validateRequired: function () {
            if (this.model.get('status') === 'Draft') {
                return false;
            }

            return Dep.prototype.validateRequired.call(this);
        },
    });
});
PK]�_��%views/email/fields/select-template.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email/fields/select-template', ['views/fields/link'], function (Dep) {

    return Dep.extend({

        type: 'link',

        foreignScope: 'EmailTemplate',

        editTemplate: 'email/fields/select-template/edit',

        setup: function () {
            Dep.prototype.setup.call(this);

            this.on('change', () => {
                let id = this.model.get(this.idName);

                if (id) {
                    this.loadTemplate(id);
                }
            });
        },

        getSelectPrimaryFilterName: function () {
            return 'actual';
        },

        loadTemplate: function (id) {
            let to = this.model.get('to') || '';
            let emailAddress = null;

            to = to.trim();

            if (to) {
                emailAddress = to.split(';')[0].trim();
            }

            Espo.Ajax
                .postRequest(`EmailTemplate/${id}/prepare`, {
                    emailAddress: emailAddress,
                    parentType: this.model.get('parentType'),
                    parentId: this.model.get('parentId'),
                    relatedType: this.model.get('relatedType'),
                    relatedId: this.model.get('relatedId'),
                })
                .then(data => {
                    this.model.trigger('insert-template', data);

                    this.emptyField();
                })
                .catch(() => {
                    this.emptyField();
                });
        },

        emptyField: function () {
            this.model.set(this.idName, null);
            this.model.set(this.nameName, '');
        },
    });
});
PK]��"N�	�	$views/email/fields/has-attachment.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email/fields/has-attachment', ['views/fields/base'], function (Dep) {

    /**
     * @class
     * @name Class
     * @extends module:views/fields/base
     * @memberOf module:views/email/fields/has-attachment
     */
    return Dep.extend(/** @lends module:views/email/fields/has-attachment.Class# */{

        listTemplate: 'email/fields/has-attachment/detail',
        detailTemplate: 'email/fields/has-attachment/detail',

        events: {
            'click [data-action="show"]': function (e) {
                e.stopPropagation();

                this.show();
            },
        },

        data: function () {
            let data = Dep.prototype.data.call(this);

            data.isSmall = this.mode === this.MODE_LIST;

            return data;
        },

        show: function () {
            Espo.Ui.notify(' ... ');

            this.createView('dialog', 'views/email/modals/attachments', {model: this.model})
                .then(view => {
                    view.render();

                    Espo.Ui.notify(false);
                });
        },
    });
});
PK]�f����views/email/fields/replied.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email/fields/replied', ['views/fields/link'], function (Dep) {

    return Dep.extend({

        afterRender: function () {
            Dep.prototype.afterRender.call(this);

            if (this.mode === 'detail') {
                var $a = this.$el.find('a');
                if ($a.get(0)) {
                    $(
                        '<span class="fas fa-arrow-left fa-sm link-field-icon text-soft"></span>'
                    ).insertBefore($a);
                }
            }
        },
    });
});
PK]P�Y3�I�I*views/email/fields/from-address-varchar.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define(
    'views/email/fields/from-address-varchar',
    ['views/fields/base', 'views/email/fields/email-address', 'helpers/record-modal'],
    function (Dep, EmailAddress, RecordModal) {

    return Dep.extend({

        detailTemplate: 'email/fields/email-address-varchar/detail',

        validations: ['required', 'email'],

        skipCurrentInAutocomplete: true,

        setup: function () {
            Dep.prototype.setup.call(this);

            this.erasedPlaceholder = 'ERASED:';

            this.on('render', () => {
                if (this.mode === this.MODE_SEARCH) {
                    return;
                }

                this.initAddressList();
            });
        },

        events: {
            'click [data-action="createContact"]': function (e) {
                var address = $(e.currentTarget).data('address');
                this.createPerson('Contact', address);
            },
            'click [data-action="createLead"]': function (e) {
                var address = $(e.currentTarget).data('address');
                this.createPerson('Lead', address);
            },
            'click [data-action="addToContact"]': function (e) {
                var address = $(e.currentTarget).data('address');
                this.addToPerson('Contact', address);
            },
            'click [data-action="addToLead"]': function (e) {
                var address = $(e.currentTarget).data('address');
                this.addToPerson('Lead', address);
            },
            'auxclick a[href][data-scope][data-id]': function (e) {
                let isCombination = e.button === 1 && (e.ctrlKey || e.metaKey);

                if (!isCombination) {
                    return;
                }

                let $target = $(e.currentTarget);

                let id = $target.attr('data-id');
                let scope = $target.attr('data-scope');

                e.preventDefault();
                e.stopPropagation();

                this.quickView({
                    id: id,
                    scope: scope,
                });
            },
        },

        data: function () {
            var data = Dep.prototype.data.call(this);

            var address = this.model.get(this.name);
            if (address && !(address in this.idHash) && this.model.get('parentId')) {
                if (this.getAcl().check('Contact', 'edit')) {
                    data.showCreate = true;
                }
            }

            data.valueIsSet = this.model.has(this.name);

            return data;
        },

        afterRender: function () {
            Dep.prototype.afterRender.call(this);

            if (this.mode === this.MODE_SEARCH && this.getAcl().check('Email', 'create')) {
                EmailAddress.prototype.initSearchAutocomplete.call(this);
            }

            if (this.mode === this.MODE_EDIT && this.getAcl().check('Email', 'create')) {
                EmailAddress.prototype.initSearchAutocomplete.call(this);
            }

            if (this.mode === this.MODE_SEARCH) {
                this.$input.on('input', () => {
                    this.trigger('change');
                });
            }
        },

        getAutocompleteMaxCount: function () {
            return EmailAddress.prototype.getAutocompleteMaxCount.call(this);
        },

        initAddressList: function () {
            this.nameHash = {};
            this.typeHash = this.model.get('typeHash') || {};
            this.idHash = this.model.get('idHash') || {};

            _.extend(this.nameHash, this.model.get('nameHash') || {});
        },

        getAttributeList: function () {
            var list = Dep.prototype.getAttributeList.call(this);

            list.push('nameHash');
            list.push('idHash');
            list.push('accountId');

            return list;
        },

        getValueForDisplay: function () {
            if (this.mode === this.MODE_DETAIL) {
                var address = this.model.get(this.name);

                return this.getDetailAddressHtml(address);
            }

            return Dep.prototype.getValueForDisplay.call(this);
        },

        getDetailAddressHtml: function (address) {
            if (!address) {
                return '';
            }

            let fromString = this.model.get('fromString') || this.model.get('fromName');

            let name = this.nameHash[address] || this.parseNameFromStringAddress(fromString) || null;

            let entityType = this.typeHash[address] || null;
            let id = this.idHash[address] || null;

            if (id) {
                return $('<div>')
                    .append(
                        $('<a>')
                            .attr('href', `#${entityType}/view/${id}`)
                            .attr('data-scope', entityType)
                            .attr('data-id', id)
                            .text(name),
                        ' ',
                        $('<span>').addClass('text-muted chevron-right'),
                        ' ',
                        $('<span>').text(address)
                    )
                    .get(0).outerHTML;
            }

            let $div = $('<div>');

            if (this.getAcl().check('Contact', 'create') || this.getAcl().check('Lead', 'create')) {
                $div.append(
                    this.getCreateHtml(address)
                );
            }

            if (name) {
                $div.append(
                    $('<span>')
                        .addClass('email-address-line')
                        .text(name)
                        .append(
                            ' ',
                            $('<span>').addClass('text-muted chevron-right'),
                            ' ',
                            $('<span>').text(address)
                        )
                );

                return $div.get(0).outerHTML;
            }

            $div.append(
                $('<span>')
                    .addClass('email-address-line')
                    .text(address)
            )


            return $div.get(0).outerHTML;
        },

        getCreateHtml: function (address) {
            let $ul = $('<ul>')
                .addClass('dropdown-menu')
                .attr('role', 'menu');

            let $container = $('<span>')
                .addClass('dropdown email-address-create-dropdown pull-right')
                .append(
                    $('<button>')
                        .addClass('dropdown-toggle btn btn-link btn-sm')
                        .attr('data-toggle', 'dropdown')
                        .append(
                            $('<span>').addClass('caret text-muted')
                        ),
                    $ul
                );

            if (this.getAcl().check('Contact', 'create')) {
                $ul.append(
                    $('<li>')
                        .append(
                            $('<a>')
                                .attr('role', 'button')
                                .attr('tabindex', '0')
                                .attr('data-action', 'createContact')
                                .attr('data-address', address)
                                .text(this.translate('Create Contact', 'labels', 'Email'))
                        )
                );
            }

            if (this.getAcl().check('Lead', 'create')) {
                $ul.append(
                    $('<li>')
                        .append(
                            $('<a>')
                                .attr('role', 'button')
                                .attr('tabindex', '0')
                                .attr('data-action', 'createLead')
                                .attr('data-address', address)
                                .text(this.translate('Create Lead', 'labels', 'Email'))
                        )
                );
            }

            if (this.getAcl().check('Contact', 'edit')) {
                $ul.append(
                    $('<li>')
                        .append(
                            $('<a>')
                                .attr('role', 'button')
                                .attr('tabindex', '0')
                                .attr('data-action', 'addToContact')
                                .attr('data-address', address)
                                .text(this.translate('Add to Contact', 'labels', 'Email'))
                        )
                );
            }

            if (this.getAcl().check('Lead', 'edit')) {
                $ul.append(
                    $('<li>')
                        .append(
                            $('<a>')
                                .attr('role', 'button')
                                .attr('tabindex', '0')
                                .attr('data-action', 'addToLead')
                                .attr('data-address', address)
                                .text(this.translate('Add to Lead', 'labels', 'Email'))
                        )
                );
            }

            return $container.get(0).outerHTML;
        },

        parseNameFromStringAddress: function (value) {
            value = value || '';

            if (~value.indexOf('<')) {
                var name = value.replace(/<(.*)>/, '').trim();

                if (name.charAt(0) === '"' && name.charAt(name.length - 1) === '"') {
                    name = name.substr(1, name.length - 2);
                }

                return name;
            }

            return null;
        },

        createPerson: function (scope, address) {
            var fromString = this.model.get('fromString') || this.model.get('fromName');
            var name = this.nameHash[address] || null;

            if (!name) {
                if (this.name === 'from') {
                    name = this.parseNameFromStringAddress(fromString) || null;
                }
            }

            if (name) {
                name = this.getHelper().escapeString(name);
            }

            var attributes = {
                emailAddress: address
            };

            if (this.model.get('accountId') && scope === 'Contact') {
                attributes.accountId = this.model.get('accountId');
                attributes.accountName = this.model.get('accountName');
            }

            if (name) {
                var firstName = name.split(' ').slice(0, -1).join(' ');
                var lastName = name.split(' ').slice(-1).join(' ');

                attributes.firstName = firstName;
                attributes.lastName = lastName;
            }

            var viewName = this.getMetadata().get('clientDefs.' + scope + '.modalViews.edit') ||
                'views/modals/edit';

            this.createView('create', viewName, {
                scope: scope,
                attributes: attributes
            }, (view) => {
                view.render();

                this.listenTo(view, 'after:save', (model) => {
                    var nameHash = Espo.Utils.clone(this.model.get('nameHash') || {});
                    var typeHash = Espo.Utils.clone(this.model.get('typeHash') || {});
                    var idHash = Espo.Utils.clone(this.model.get('idHash') || {});

                    idHash[address] = model.id;
                    nameHash[address] = model.get('name');
                    typeHash[address] = scope;

                    this.idHash = idHash;
                    this.nameHash = nameHash;
                    this.typeHash = typeHash;

                    var attributes = {
                        nameHash: nameHash,
                        idHash: idHash,
                        typeHash: typeHash
                    };

                    setTimeout(() => {
                        this.model.set(attributes);

                        if (this.model.get('icsContents')) {
                            this.model.fetch();
                        }
                    }, 50);
                });
            });
        },

        addToPerson: function (scope, address) {
            var fromString = this.model.get('fromString') || this.model.get('fromName');
            var name = this.nameHash[address] || null;

            if (!name) {
                if (this.name === 'from') {
                    name = this.parseNameFromStringAddress(fromString) || null;
                }
            }

            if (name) {
                name = this.getHelper().escapeString(name);
            }

            var attributes = {
                emailAddress: address,
            };

            if (this.model.get('accountId') && scope === 'Contact') {
                attributes.accountId = this.model.get('accountId');
                attributes.accountName = this.model.get('accountName');
            }

            var viewName = this.getMetadata().get('clientDefs.' + scope + '.modalViews.select') ||
                'views/modals/select-records';

            Espo.Ui.notify(' ... ');

            var filters = {};

            if (name) {
                filters['name'] = {
                    type: 'equals',
                    field: 'name',
                    value: name,
                };
            }

            this.createView('dialog', viewName, {
                scope: scope,
                createButton: false,
                filters: filters,
            }, (view) => {
                view.render();

                Espo.Ui.notify(false);

                this.listenToOnce(view, 'select', (model) => {
                    var afterSave = () => {
                        var nameHash = Espo.Utils.clone(this.model.get('nameHash') || {});
                        var typeHash = Espo.Utils.clone(this.model.get('typeHash') || {});
                        var idHash = Espo.Utils.clone(this.model.get('idHash') || {});

                        idHash[address] = model.id;
                        nameHash[address] = model.get('name');
                        typeHash[address] = scope;

                        this.idHash = idHash;
                        this.nameHash = nameHash;
                        this.typeHash = typeHash;

                        var attributes = {
                            nameHash: nameHash,
                            idHash: idHash,
                            typeHash: typeHash
                        };

                        setTimeout(() => {
                            this.model.set(attributes);

                            if (this.model.get('icsContents')) {
                                this.model.fetch();
                            }
                        }, 50);
                    };

                    if (!model.get('emailAddress')) {
                        model.save({
                            'emailAddress': address
                        }, {patch: true}).then(afterSave);
                    }
                    else {
                        model.fetch().then(() => {
                            var emailAddressData = model.get('emailAddressData') || [];

                            var item = {
                                emailAddress: address,
                                primary: emailAddressData.length === 0
                            };

                            emailAddressData.push(item);

                            model.save({
                                'emailAddressData': emailAddressData
                            }, {patch: true}).then(afterSave);
                        });
                    }
                });
            });
        },

        fetchSearch: function () {
            var value = this.$element.val().trim();

            if (value) {
                return {
                    type: 'equals',
                    value: value,
                }
            }

            return null;
        },

        validateEmail: function () {
            var address = this.model.get(this.name);

            if (!address) {
                return;
            }

            var addressLowerCase = String(address).toLowerCase();

            var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

            if (!re.test(addressLowerCase) && address.indexOf(this.erasedPlaceholder) !== 0) {
                var msg = this.translate('fieldShouldBeEmail', 'messages')
                    .replace('{field}', this.getLabelText());

                this.showValidationMessage(msg);

                return true;
            }
        },

        quickView: function (data) {
            let helper = new RecordModal(this.getMetadata(), this.getAcl());

            helper.showDetail(this, {
                id: data.id,
                scope: data.scope,
            });
        },
    });
});
PK]T1	���*views/email/fields/compose-from-address.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email/fields/compose-from-address', ['views/fields/base', 'ui/select'],
function (Dep, /** module:ui/select*/Select) {

    return Dep.extend({

        detailTemplate: 'email/fields/email-address-varchar/detail',
        editTemplate: 'email/fields/compose-from-address/edit',

        data: function () {
            let noSmtpMessage = this.translate('noSmtpSetup', 'messages', 'Email');

            let linkHtml = $('<a>')
                    .attr('href', '#EmailAccount')
                    .text(this.translate('EmailAccount', 'scopeNamesPlural'))
                    .get(0).outerHTML;

            noSmtpMessage = noSmtpMessage.replace('{link}', linkHtml);

            return {
                list: this.list,
                noSmtpMessage: noSmtpMessage,
                ...Dep.prototype.data.call(this),
            };
        },

        setup: function () {
            Dep.prototype.setup.call(this);

            this.nameHash = {...(this.model.get('nameHash') || {})};
            this.typeHash = this.model.get('typeHash') || {};
            this.idHash = this.model.get('idHash') || {};

            this.list = this.getUser().get('emailAddressList') || [];
        },

        afterRenderEdit: function () {
            if (this.$element.length) {
                Select.init(this.$element);
            }
        },

        getValueForDisplay: function () {
            if (this.isDetailMode()) {
                let address = this.model.get(this.name);

                return this.getDetailAddressHtml(address);
            }

            return Dep.prototype.getValueForDisplay.call(this);
        },

        getDetailAddressHtml: function (address) {
            if (!address) {
                return '';
            }

            let name = this.nameHash[address] || null;

            let entityType = this.typeHash[address] || null;
            let id = this.idHash[address] || null;

            if (id && name) {
                return $('<div>')
                    .append(
                        $('<a>')
                            .attr('href', `#${entityType}/view/${id}`)
                            .attr('data-scope', entityType)
                            .attr('data-id', id)
                            .text(name),
                        ' ',
                        $('<span>').addClass('text-muted chevron-right'),
                        ' ',
                        $('<span>').text(address)
                    )
                    .get(0).outerHTML;
            }

            let $div = $('<div>');

            if (name) {
                $div.append(
                    $('<span>')
                        .addClass('email-address-line')
                        .text(name)
                        .append(
                            ' ',
                            $('<span>').addClass('text-muted chevron-right'),
                            ' ',
                            $('<span>').text(address)
                        )
                );

                return $div.get(0).outerHTML;
            }

            $div.append(
                $('<span>')
                    .addClass('email-address-line')
                    .text(address)
            )

            return $div.get(0).outerHTML;
        },
    });
});
PK]u8d�T�Tviews/email/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import DetailView from 'views/detail';
import EmailHelper from 'email-helper';

class EmailDetailView extends DetailView {

    setup() {
        super.setup();

        let status = this.model.get('status');

        if (status === 'Draft') {
            this.menu = {
                'buttons': [],
                'dropdown': [],
                'actions': []
            };
        }
        else {
            this.addMenuItem('buttons', {
                name: 'reply',
                label: 'Reply',
                action: this.getPreferences().get('emailReplyToAllByDefault') ? 'replyToAll' : 'reply',
                style: 'danger',
                className: 'btn-s-wide',
            }, true);

            this.addMenuItem('dropdown', false);

            if (status === 'Archived') {
                if (!this.model.get('parentId')) {
                    this.addMenuItem('dropdown', {
                        label: 'Create Lead',
                        action: 'createLead',
                        acl: 'create',
                        aclScope: 'Lead',
                    });

                    this.addMenuItem('dropdown', {
                        label: 'Create Contact',
                        action: 'createContact',
                        acl: 'create',
                        aclScope: 'Contact',
                    });
                }
            }

            this.addMenuItem('dropdown', {
                label: 'Create Task',
                action: 'createTask',
                acl: 'create',
                aclScope: 'Task'
            });

            if (this.model.get('parentType') !== 'Case' || !this.model.get('parentId')) {
                this.addMenuItem('dropdown', {
                    label: 'Create Case',
                    action: 'createCase',
                    acl: 'create',
                    aclScope: 'Case'
                });
            }

            if (this.getAcl().checkScope('Document', 'create')) {
                if (
                    this.model.get('attachmentsIds') === undefined ||
                    this.model.getLinkMultipleIdList('attachments').length
                ) {
                    this.addMenuItem('dropdown', {
                        text: this.translate('Create Document', 'labels', 'Document'),
                        action: 'createDocument',
                        acl: 'create',
                        aclScope: 'Document',
                        hidden: this.model.get('attachmentsIds') === undefined,
                    });

                    if (this.model.get('attachmentsIds') === undefined) {
                        this.listenToOnce(this.model, 'sync', () => {
                            if (this.model.getLinkMultipleIdList('attachments').length) {
                                this.showHeaderActionItem('createDocument');
                            }
                        });
                    }
                }
            }
        }

        this.listenTo(this.model, 'change', () => {
            if (!this.isRendered()) {
                return;
            }

            if (!this.model.hasChanged('isImportant') && !this.model.hasChanged('inTrash')) {
                return;
            }

            let headerView = this.getHeaderView();

            if (headerView) {
                headerView.reRender();
            }
        });

        this.shortcutKeys['Control+Delete'] = e => {
            if ($(e.target).hasClass('note-editable')) {
                return;
            }

            let recordView = /** @type {module:views/email/record/detail} */ this.getRecordView();

            if (!this.model.get('isUsers') || this.model.get('inTrash')) {
                return;
            }

            e.preventDefault();
            e.stopPropagation();

            recordView.actionMoveToTrash();
        };

        this.shortcutKeys['Control+KeyI'] = e => {
            if ($(e.target).hasClass('note-editable')) {
                return;
            }

            let recordView = /** @type {module:views/email/record/detail} */ this.getRecordView();

            if (!this.model.get('isUsers')) {
                return;
            }

            e.preventDefault();
            e.stopPropagation();

            this.model.get('isImportant') ?
                recordView.actionMarkAsNotImportant() :
                recordView.actionMarkAsImportant();
        };

        this.shortcutKeys['Control+KeyM'] = e => {
            if ($(e.target).hasClass('note-editable')) {
                return;
            }

            let recordView = /** @type {module:views/email/record/detail} */ this.getRecordView();

            if (!this.model.get('isUsers')) {
                return;
            }

            e.preventDefault();
            e.stopPropagation();

            recordView.actionMoveToFolder();
        };
    }

    // noinspection JSUnusedGlobalSymbols
    actionCreateLead() {
        let attributes = {};

        let emailHelper = new EmailHelper(
            this.getLanguage(),
            this.getUser(),
            this.getDateTime(),
            this.getAcl()
        );

        let fromString = this.model.get('fromString') || this.model.get('fromName');

        if (fromString) {
            let fromName = emailHelper.parseNameFromStringAddress(fromString);

            if (fromName) {
                let firstName = fromName.split(' ').slice(0, -1).join(' ');
                let lastName = fromName.split(' ').slice(-1).join(' ');

                attributes.firstName = firstName;
                attributes.lastName = lastName;
            }
        }

        if (this.model.get('replyToString')) {
            let str = this.model.get('replyToString');
            let p = (str.split(';'))[0];

            attributes.emailAddress = emailHelper.parseAddressFromStringAddress(p);

            let fromName = emailHelper.parseNameFromStringAddress(p);

            if (fromName) {
                let firstName = fromName.split(' ').slice(0, -1).join(' ');
                let lastName = fromName.split(' ').slice(-1).join(' ');

                attributes.firstName = firstName;
                attributes.lastName = lastName;
            }
        }

        if (!attributes.emailAddress) {
            attributes.emailAddress = this.model.get('from');
        }

        attributes.emailId = this.model.id;

        let viewName = this.getMetadata().get('clientDefs.Lead.modalViews.edit') || 'views/modals/edit';

        Espo.Ui.notify(' ... ');

        this.createView('quickCreate', viewName, {
            scope: 'Lead',
            attributes: attributes,
        }, view => {
            view.render();
            view.notify(false);

            this.listenTo(view, 'before:save', () => {
                this.getRecordView().blockUpdateWebSocket(true);
            });

            this.listenToOnce(view, 'after:save', () => {
                this.model.fetch();
                this.removeMenuItem('createContact');
                this.removeMenuItem('createLead');

                view.close();
            });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionCreateCase() {
        let attributes = {};

        let parentId = this.model.get('parentId');
        let parentType = this.model.get('parentType');
        let parentName = this.model.get('parentName');

        let accountId = this.model.get('accountId');
        let accountName = this.model.get('accountName');

        if (parentId) {
            if (parentType === 'Account') {
                attributes.accountId = parentId;
                attributes.accountName = parentName;
            }
            else if (parentType === 'Contact') {
                attributes.contactId = parentId;
                attributes.contactName = parentName;

                attributes.contactsIds = [parentId];
                attributes.contactsNames = {};
                attributes.contactsNames[parentId] = parentName;

                if (accountId) {
                    attributes.accountId = accountId;
                    attributes.accountName = accountName || accountId;
                }
            }
            else if (parentType === 'Lead') {
                attributes.leadId = parentId;
                attributes.leadName = parentName;
            }
        }

        attributes.emailsIds = [this.model.id];
        attributes.emailId = this.model.id;
        attributes.name = this.model.get('name');
        attributes.description = this.model.get('bodyPlain') || '';

        let viewName = this.getMetadata().get('clientDefs.Case.modalViews.edit') || 'views/modals/edit';

        Espo.Ui.notify(' ... ');

        (new Promise(resolve => {
            if (!(this.model.get('attachmentsIds') || []).length) {
                resolve();

                return;
            }

            Espo.Ajax.postRequest(`Email/${this.model.id}/attachments/copy`, {
                parentType: 'Case',
                field: 'attachments',
            }).then(data => {
                attributes.attachmentsIds = data.ids;
                attributes.attachmentsNames = data.names;

                resolve();
            });
        })).then(() => {
            this.createView('quickCreate', viewName, {
                scope: 'Case',
                attributes: attributes,
            }, view => {
                view.render();

                Espo.Ui.notify(false);

                this.listenToOnce(view, 'after:save', () => {
                    this.model.fetch();
                    this.removeMenuItem('createCase');

                    view.close();
                });

                this.listenTo(view, 'before:save', () => {
                    this.getRecordView().blockUpdateWebSocket(true);
                });
            });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionCreateTask() {
        let attributes = {};

        attributes.parentId = this.model.get('parentId');
        attributes.parentName = this.model.get('parentName');
        attributes.parentType = this.model.get('parentType');
        attributes.emailId = this.model.id;

        let subject = this.model.get('name');

        attributes.description = '[' + this.translate('Email', 'scopeNames') + ': ' + subject +']' +
            '(#Email/view/' + this.model.id + ')\n';

        let viewName = this.getMetadata().get('clientDefs.Task.modalViews.edit') || 'views/modals/edit';

        Espo.Ui.notify(' ... ');

        this.createView('quickCreate', viewName, {
            scope: 'Task',
            attributes: attributes,
        }, view => {
            let recordView = view.getRecordView();

            let nameFieldView = recordView.getFieldView('name');

            let nameOptionList = [];

            if (nameFieldView && nameFieldView.params.options) {
                nameOptionList = nameOptionList.concat(nameFieldView.params.options);
            }

            nameOptionList.push(this.translate('replyToEmail', 'nameOptions', 'Task'));

            recordView.setFieldOptionList('name', nameOptionList);

            view.render();

            view.notify(false);

            this.listenToOnce(view, 'after:save', () => {
                view.close();

                this.model.fetch();
            });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionCreateContact() {
        let attributes = {};

        let emailHelper = new EmailHelper(
            this.getLanguage(),
            this.getUser(),
            this.getDateTime(),
            this.getAcl()
        );

        let fromString = this.model.get('fromString') || this.model.get('fromName');

        if (fromString) {
            let fromName = emailHelper.parseNameFromStringAddress(fromString);

            if (fromName) {
                let firstName = fromName.split(' ').slice(0, -1).join(' ');
                let lastName = fromName.split(' ').slice(-1).join(' ');

                attributes.firstName = firstName;
                attributes.lastName = lastName;
            }
        }

        if (this.model.get('replyToString')) {
            let str = this.model.get('replyToString');
            let p = (str.split(';'))[0];

            attributes.emailAddress = emailHelper.parseAddressFromStringAddress(p);

            let fromName = emailHelper.parseNameFromStringAddress(p);

            if (fromName) {
                let firstName = fromName.split(' ').slice(0, -1).join(' ');
                let lastName = fromName.split(' ').slice(-1).join(' ');

                attributes.firstName = firstName;
                attributes.lastName = lastName;
            }
        }

        if (!attributes.emailAddress) {
            attributes.emailAddress = this.model.get('from');
        }

        attributes.emailId = this.model.id;

        let viewName = this.getMetadata().get('clientDefs.Contact.modalViews.edit') || 'views/modals/edit';

        Espo.Ui.notify(' ... ');

        this.createView('quickCreate', viewName, {
            scope: 'Contact',
            attributes: attributes,
        }, (view) => {
            view.render();

            view.notify(false);

            this.listenToOnce(view, 'after:save', () => {
                this.model.fetch();
                this.removeMenuItem('createContact');
                this.removeMenuItem('createLead');

                view.close();
            });

            this.listenTo(view, 'before:save', () => {
                this.getRecordView().blockUpdateWebSocket(true);
            });
        });
    }

    actionReply(data, e, cc) {
        let emailHelper = new EmailHelper(
            this.getLanguage(),
            this.getUser(),
            this.getDateTime(),
            this.getAcl()
        );

        let attributes = emailHelper.getReplyAttributes(this.model, data, cc);

        Espo.Ui.notify(' ... ');

        let viewName = this.getMetadata().get('clientDefs.Email.modalViews.compose') ||
            'views/modals/compose-email';

        this.createView('quickCreate', viewName, {
            attributes: attributes,
            focusForCreate: true,
        }, view => {
            view.render();

            view.notify(false);

            this.listenTo(view, 'after:save', () => {
                this.model.fetch();
            });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionReplyToAll(data, e) {
        this.actionReply(data, e, true);
    }

    // noinspection JSUnusedGlobalSymbols
    actionForward() {
        let emailHelper = new EmailHelper(
            this.getLanguage(),
            this.getUser(),
            this.getDateTime(),
            this.getAcl()
        );

        Espo.Ui.notify(' ... ');

        Espo.Ajax
            .postRequest('Email/action/getDuplicateAttributes', {
                id: this.model.id,
            })
            .then(duplicateAttributes => {
                let model = this.model.clone();

                model.set('body', duplicateAttributes.body);

                let attributes = emailHelper.getForwardAttributes(model);

                attributes.attachmentsIds = duplicateAttributes.attachmentsIds;
                attributes.attachmentsNames = duplicateAttributes.attachmentsNames;

                Espo.Ui.notify(' ... ');

                let viewName = this.getMetadata().get('clientDefs.Email.modalViews.compose') ||
                    'views/modals/compose-email';

                this.createView('quickCreate', viewName, {
                    attributes: attributes,
                }, view => {
                    view.render();

                    view.notify(false);
                });
            });
    }

    getHeader() {
        let name = this.model.get('name');

        let isImportant = this.model.get('isImportant');
        let inTrash = this.model.get('inTrash');

        let rootUrl = this.options.rootUrl || this.options.params.rootUrl || '#' + this.scope;

        let headerIconHtml = this.getHeaderIconHtml();

        let $root = $('<a>')
            .attr('href', rootUrl)
            .attr('data-action', 'navigateToRoot')
            .addClass('action')
            .text(
                this.getLanguage().translate(this.model.name, 'scopeNamesPlural')
            );

        if (headerIconHtml) {
            $root = $('<span>')
                .append(headerIconHtml, $root)
                .get(0).innerHTML;
        }

        return this.buildHeaderHtml([
            $root,
            $('<span>')
                .addClass('font-size-flexible title')
                .addClass(isImportant ? 'text-warning' : '')
                .addClass(inTrash ? 'text-muted' : '')
                .text(name),
        ]);
    }

    actionNavigateToRoot(data, event) {
        event.stopPropagation();

        this.getRouter().checkConfirmLeaveOut(() => {
            let rootUrl = this.options.rootUrl || this.options.params.rootUrl || '#' + this.scope;

            let options = {
                isReturn: true,
                isReturnThroughLink: true,
            };

            this.getRouter().navigate(rootUrl, {trigger: false});
            this.getRouter().dispatch(this.scope, null, options);
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionCreateDocument() {
        let attachmentIdList = this.model.getLinkMultipleIdList('attachments');

        if (!attachmentIdList.length) {
            return;
        }

        let names = this.model.get('attachmentsNames') || {};
        let types = this.model.get('attachmentsTypes') || {};

        let proceed = (id) => {
            let attributes = {};

            if (this.model.get('accountId')) {
                attributes.accountsIds = [this.model.get('accountId')];
                attributes.accountsNames = {};
                attributes.accountsNames[this.model.get('accountId')] = this.model.get('accountName');
            }

            Espo.Ui.notify(' ... ');

            Espo.Ajax.postRequest('Attachment/copy/' + id, {
                relatedType: 'Document',
                field: 'file',
            }).then((attachment) => {
                attributes.fileId = attachment.id;
                attributes.fileName = attachment.name;
                attributes.name = attachment.name;

                let viewName = this.getMetadata().get('clientDefs.Document.modalViews.edit') ||
                    'views/modals/edit';

                this.createView('quickCreate', viewName, {
                    scope: 'Document',
                    attributes: attributes,
                }, (view) => {
                    view.render();

                    Espo.Ui.notify(false);

                    this.listenToOnce(view, 'after:save', () => {
                        view.close();
                    });
                });
            });
        };

        if (attachmentIdList.length === 1) {
            proceed(attachmentIdList[0]);

            return;
        }

        let dataList = [];

        attachmentIdList.forEach((id) => {
            dataList.push({
                id: id,
                name: names[id] || id,
                type: types[id],
            });
        });

        this.createView('dialog', 'views/attachment/modals/select-one', {
            dataList: dataList,
            fieldLabel: this.translate('attachments', 'fields', 'Email'),
        }, view => {
            view.render();

            this.listenToOnce(view, 'select', proceed.bind(this));
        });
    }
}

export default EmailDetailView;
PK]j�� views/email/record/edit-quick.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email/record/edit-quick', ['views/email/record/edit'], function (Dep) {

    return Dep.extend({

    	isWide: true,
        sideView: false,
    });
});
PK]>ӽ���"views/email/record/panels/event.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email/record/panels/event', ['views/record/panels/side'], function (Dep) {

    return class extends Dep {

        setupFields() {
            super.setupFields();

            this.fieldList.push({
                name: 'icsEventDateStart',
                readOnly: true,
                labelText: this.translate('dateStart', 'fields', 'Meeting'),
            });

            this.fieldList.push({
                name: 'createdEvent',
                readOnly: true,
            });

            this.fieldList.push({
                name: 'createEvent',
                readOnly: true,
                noLabel: true,
            });

            this.controlEventField();

            this.listenTo(this.model, 'change:icsEventData', this.controlEventField, this);
            this.listenTo(this.model, 'change:createdEventId', this.controlEventField, this);
        }

        controlEventField() {
            if (!this.model.get('icsEventData')) {
                this.recordViewObject.hideField('createEvent');
                this.recordViewObject.showField('createdEvent');

                return;
            }

            let eventData = this.model.get('icsEventData');

            if (eventData.createdEvent) {
                this.recordViewObject.hideField('createEvent');
                this.recordViewObject.showField('createdEvent');

                return;
            }

            if (!this.model.get('createdEventId')) {
                this.recordViewObject.hideField('createdEvent');
                this.recordViewObject.showField('createEvent');

                return;
            }

            this.recordViewObject.hideField('createEvent');
            this.recordViewObject.showField('createdEvent');
        }
    };
});
PK]�w�9��)views/email/record/panels/default-side.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email/record/panels/default-side', ['views/record/panels/default-side'], function (Dep) {

    return Dep.extend({

        setupFields: function () {
            Dep.prototype.setupFields.call(this);

            this.fieldList.push({
                name: 'hasAttachment',
                view: 'views/email/fields/has-attachment',
                noLabel: true,
            });

            this.controlHasAttachmentField();

            this.listenTo(this.model, 'change:hasAttachment', this.controlHasAttachmentField, this);
        },

        controlHasAttachmentField: function () {
            if (this.model.get('hasAttachment')) {
                this.recordViewObject.showField('hasAttachment');

                return;
            }

            this.recordViewObject.hideField('hasAttachment');
        },
    });
});
PK]%�M??)views/email/record/row-actions/default.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email/record/row-actions/default', ['views/record/row-actions/default'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);
            this.listenTo(this.model, 'change', function (model) {
                if (model.hasChanged('isImportant') || model.hasChanged('inTrash')) {
                    setTimeout(function () {
                        this.reRender();
                    }.bind(this), 10);
                }
            }, this);
        },

        getActionList: function () {
            var list = [{
                action: 'quickView',
                label: 'View',
                data: {
                    id: this.model.id
                }
            }];

            if (
                this.model.get('createdById') === this.getUser().id && this.model.get('status') === 'Draft' &&
                !this.model.get('inTrash')
            ) {
                list.push({
                    action: 'send',
                    label: 'Send',
                    data: {
                        id: this.model.id,
                    },
                });
            }

            if (this.options.acl.edit) {
                list = list.concat([
                    {
                        action: 'quickEdit',
                        label: 'Edit',
                        data: {
                            id: this.model.id
                        }
                    }
                ]);
            }

            if (this.model.get('isUsers') && this.model.get('status') !== 'Draft') {
                if (!this.model.get('inTrash')) {
                    list.push({
                        action: 'moveToTrash',
                        label: 'Move to Trash',
                        data: {
                            id: this.model.id
                        }
                    });
                } else {
                    list.push({
                        action: 'retrieveFromTrash',
                        label: 'Retrieve from Trash',
                        data: {
                            id: this.model.id
                        }
                    });
                }
            }

            if (this.model.get('isUsers')) {
                if (!this.model.get('isImportant')) {
                    if (!this.model.get('inTrash')) {
                        list.push({
                            action: 'markAsImportant',
                            label: 'Mark as Important',
                            data: {
                                id: this.model.id
                            }
                        });
                    }
                } else {
                    list.push({
                        action: 'markAsNotImportant',
                        label: 'Unmark Importance',
                        data: {
                            id: this.model.id
                        }
                    });
                }
            }

            if (this.model.get('isUsers') && this.model.get('status') !== 'Draft') {
                list.push({
                    action: 'moveToFolder',
                    label: 'Move to Folder',
                    data: {
                        id: this.model.id
                    }
                });
            }

            if (this.options.acl.delete) {
                list.push({
                    action: 'quickRemove',
                    label: 'Remove',
                    data: {
                        id: this.model.id
                    }
                });
            }

            return list;
        },

    });
});
PK]C�S�aa)views/email/record/row-actions/dashlet.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email/record/row-actions/dashlet', ['views/record/row-actions/default'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            this.listenTo(this.model, 'change:isImportant', () => {
                setTimeout(() => {
                    this.reRender();
                }, 10);
            });
        },

        getActionList: function () {
            var list = [{
                action: 'quickView',
                label: 'View',
                data: {
                    id: this.model.id
                }
            }];

            if (this.options.acl.edit) {
                list = list.concat([
                    {
                        action: 'quickEdit',
                        label: 'Edit',
                        data: {
                            id: this.model.id
                        }
                    }
                ]);
            }

            if (this.model.get('isUsers') && this.model.get('status') !== 'Draft') {
                if (!this.model.get('inTrash')) {
                    list.push({
                        action: 'moveToTrash',
                        label: 'Move to Trash',
                        data: {
                            id: this.model.id
                        }
                    });
                } else {
                    list.push({
                        action: 'retrieveFromTrash',
                        label: 'Retrieve from Trash',
                        data: {
                            id: this.model.id
                        }
                    });
                }
            }

            if (this.getAcl().checkModel(this.model, 'delete')) {
                list.push({
                    action: 'quickRemove',
                    label: 'Remove',
                    data: {
                        id: this.model.id
                    }
                });
            }

            if (this.model.get('isUsers')) {
                if (!this.model.get('isImportant')) {
                    list.push({
                        action: 'markAsImportant',
                        label: 'Mark as Important',
                        data: {
                            id: this.model.id
                        }
                    });
                } else {
                    list.push({
                        action: 'markAsNotImportant',
                        label: 'Unmark Importance',
                        data: {
                            id: this.model.id
                        }
                    });
                }
            }

            return list;
        },
    });
});
PK]���@"views/email/record/list-related.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email/record/list-related', ['views/record/list'], function (Dep) {

    return Dep.extend({

        massActionList: ['remove', 'massUpdate'],
    });
});
PK]L�۪CCviews/email/record/edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/email/record/edit */

import EditRecordView from 'views/record/edit';
import EmailDetailRecordView from 'views/email/record/detail';

class EmailEditRecordView extends EditRecordView {

    shortcutKeyCtrlEnterAction = 'send'

    init() {
        super.init();

        EmailDetailRecordView.prototype.layoutNameConfigure.call(this);
    }

    setup() {
        super.setup();

        if (['Archived', 'Sent'].includes(this.model.get('status'))) {
            this.shortcutKeyCtrlEnterAction = 'save';
        }

        this.addButton({
            name: 'send',
            label: 'Send',
            style: 'primary',
            title: 'Ctrl+Enter',
        }, true);

        this.addButton({
            name: 'saveDraft',
            label: 'Save Draft',
            title: 'Ctrl+S',
        }, true);

        this.controlSendButton();

        if (this.model.get('status') === 'Draft') {
            this.setFieldReadOnly('dateSent');

            // Not implemented for detail view yet.
            this.hideField('selectTemplate');
        }

        this.handleAttachmentField();
        this.handleCcField();
        this.handleBccField();

        this.listenTo(this.model, 'change:attachmentsIds', () => this.handleAttachmentField());
        this.listenTo(this.model, 'change:cc', () => this.handleCcField());
        this.listenTo(this.model, 'change:bcc', () => this.handleBccField());
    }

    handleAttachmentField() {
        if (
            (this.model.get('attachmentsIds') || []).length === 0 &&
            !this.isNew &&
            this.model.get('status') !== 'Draft'
        ) {
            this.hideField('attachments');

            return;
        }

        this.showField('attachments');
    }

    handleCcField() {
        if (!this.model.get('cc') && this.model.get('status') !== 'Draft') {
            this.hideField('cc');
        } else {
            this.showField('cc');
        }
    }

    handleBccField() {
        if (!this.model.get('bcc') && this.model.get('status') !== 'Draft') {
            this.hideField('bcc');
        } else {
            this.showField('bcc');
        }
    }

    controlSendButton()  {
        let status = this.model.get('status');

        if (status === 'Draft') {
            this.showActionItem('send');
            this.showActionItem('saveDraft');
            this.hideActionItem('save');
            this.hideActionItem('saveAndContinueEditing');

            return;
        }

        this.hideActionItem('send');
        this.hideActionItem('saveDraft');
        this.showActionItem('save');
        this.showActionItem('saveAndContinueEditing');
    }

    // noinspection JSUnusedGlobalSymbols
    actionSaveDraft() {
        this.actionSaveAndContinueEditing();
    }

    // noinspection JSUnusedGlobalSymbols
    actionSend() {
        EmailDetailRecordView.prototype.send.call(this)
            .then(() => this.exit())
            .catch(() => {});
    }

    /**
     * @protected
     * @param {JQueryKeyEventObject} e
     */
    handleShortcutKeyCtrlS(e) {
        if (this.inlineEditModeIsOn || this.buttonsDisabled) {
            return;
        }

        e.preventDefault();
        e.stopPropagation();

        if (this.mode !== this.MODE_EDIT) {
            return;
        }

        if (!this.saveAndContinueEditingAction) {
            return;
        }

        if (
            !this.hasAvailableActionItem('saveDraft') &&
            !this.hasAvailableActionItem('saveAndContinueEditing')
        ) {
            return;
        }

        this.actionSaveAndContinueEditing();
    }
}

export default EmailEditRecordView;
PK]��*�C�Cviews/email/record/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/email/record/detail */

import DetailRecordView from 'views/record/detail';

class EmailDetailRecordView extends DetailRecordView {

    sideView = 'views/email/record/detail-side'
    duplicateAction = false
    shortcutKeyCtrlEnterAction = 'send'

    layoutNameConfigure() {
        if (this.model.isNew()) {
            return;
        }

        let status = this.model.get('status');

        if (status === 'Draft') {
            this.layoutName = 'composeSmall';

            return;
        }

        let isRestricted = false;

        if (status === 'Sent') {
            isRestricted = true;
        }

        if (status === 'Archived') {
            if (
                this.model.get('createdById') === this.getHelper().getAppParam('systemUserId') ||
                !this.model.get('createdById') || this.model.get('isImported')
            ) {
                isRestricted = true;
            }
        }

        if (isRestricted) {
            this.layoutName += 'Restricted';
        }

        this.isRestricted = isRestricted;
    }

    init() {
        super.init();

        this.layoutNameConfigure();
    }

    setup() {
        super.setup();

        if (['Archived', 'Sent'].includes(this.model.get('status'))) {
            this.shortcutKeyCtrlEnterAction = 'save';
        }

        this.addButtonEdit({
            name: 'send',
            action: 'send',
            label: 'Send',
            style: 'primary',
            title: 'Ctrl+Enter',
        }, true);

        this.addButtonEdit({
            name: 'saveDraft',
            action: 'save',
            label: 'Save Draft',
            title: 'Ctrl+S',
        }, true);

        this.addButton({
            name: 'sendFromDetail',
            label: 'Send',
            hidden: true,
        });

        this.controlSendButton();

        this.listenTo(this.model, 'change:status', () => this.controlSendButton());

        if (this.model.get('status') !== 'Draft' && this.model.has('isRead') && !this.model.get('isRead')) {
            this.model.set('isRead', true);
        }

        this.listenTo(this.model, 'sync', () => {
            if (!this.model.get('isRead') && this.model.get('status') !== 'Draft') {
                this.model.set('isRead', true);
            }
        });

        if (!(this.model.get('isHtml') && this.model.get('bodyPlain'))) {
            this.listenToOnce(this.model, 'sync', () => {
                if (this.model.get('isHtml') && this.model.get('bodyPlain')) {
                    this.showActionItem('showBodyPlain');
                }
            });
        }

        if (this.model.get('isUsers')) {
            this.addDropdownItem({
                'label': 'Mark as Important',
                'name': 'markAsImportant',
                'hidden': this.model.get('isImportant')
            });

            this.addDropdownItem({
                'label': 'Unmark Importance',
                'name': 'markAsNotImportant',
                'hidden': !this.model.get('isImportant')
            });

            this.addDropdownItem({
                'label': 'Move to Trash',
                'name': 'moveToTrash',
                'hidden': this.model.get('inTrash')
            });

            this.addDropdownItem({
                'label': 'Retrieve from Trash',
                'name': 'retrieveFromTrash',
                'hidden': !this.model.get('inTrash')
            });

            this.addDropdownItem({
                'label': 'Move to Folder',
                'name': 'moveToFolder'
            });
        }

        this.addDropdownItem({
            label: 'Show Plain Text',
            name: 'showBodyPlain',
            hidden: !(this.model.get('isHtml') && this.model.get('bodyPlain'))
        });

        this.addDropdownItem({
            label: 'Print',
            name: 'print',
        });

        this.listenTo(this.model, 'change:isImportant', () => {
            if (this.model.get('isImportant')) {
                this.hideActionItem('markAsImportant');
                this.showActionItem('markAsNotImportant');
            } else {
                this.hideActionItem('markAsNotImportant');
                this.showActionItem('markAsImportant');
            }
        });

        this.listenTo(this.model, 'change:inTrash', () => {
            if (this.model.get('inTrash')) {
                this.hideActionItem('moveToTrash');
                this.showActionItem('retrieveFromTrash');
            } else {
                this.hideActionItem('retrieveFromTrash');
                this.showActionItem('moveToTrash');
            }
        });

        this.handleTasksField();
        this.listenTo(this.model, 'change:tasksIds', () => this.handleTasksField());

        if (this.getUser().isAdmin()) {
            this.addDropdownItem({
                label: 'View Users',
                name: 'viewUsers'
            });
        }

        this.setFieldReadOnly('replied');

        if (this.model.get('status') === 'Draft') {
            this.setFieldReadOnly('dateSent');

            this.controlSelectTemplateField();

            this.on('after:mode-change', () => this.controlSelectTemplateField());
        }

        if (this.isRestricted) {
            this.handleAttachmentField();
            this.listenTo(this.model, 'change:attachmentsIds', () => this.handleAttachmentField());

            this.handleCcField();
            this.listenTo(this.model, 'change:cc', () => this.handleCcField());

            this.handleBccField();
            this.listenTo(this.model, 'change:bcc', () => this.handleBccField());
        }
    }

    controlSelectTemplateField() {
        if (this.mode === this.MODE_EDIT) {
            // Not implemented for detail view yet.
            this.hideField('selectTemplate');

            return;
        }

        this.hideField('selectTemplate');
    }

    controlSendButton()  {
        let status = this.model.get('status');

        if (status === 'Draft') {
            this.showActionItem('send');
            this.showActionItem('saveDraft');
            this.showActionItem('sendFromDetail');
            this.hideActionItem('save');
            this.hideActionItem('saveAndContinueEditing');

            return;
        }

        this.hideActionItem('sendFromDetail');
        this.hideActionItem('send');
        this.hideActionItem('saveDraft');
        this.showActionItem('save');
        this.showActionItem('saveAndContinueEditing');
    }

    // noinspection JSUnusedGlobalSymbols
    actionSaveDraft() {
        this.actionSaveAndContinueEditing();
    }

    actionMarkAsImportant() {
        Espo.Ajax.postRequest('Email/inbox/important', {id: this.model.id});

        this.model.set('isImportant', true);
    }

    actionMarkAsNotImportant() {
        Espo.Ajax.deleteRequest('Email/inbox/important', {id: this.model.id});

        this.model.set('isImportant', false);
    }

    actionMoveToTrash() {
        Espo.Ajax.postRequest('Email/inbox/inTrash', {id: this.model.id}).then(() => {
            Espo.Ui.warning(this.translate('Moved to Trash', 'labels', 'Email'));
        });

        this.model.set('inTrash', true);

        if (this.model.collection) {
            this.model.collection.trigger('moving-to-trash', this.model.id);
        }
    }

    // noinspection JSUnusedGlobalSymbols
    actionRetrieveFromTrash() {
        Espo.Ajax.deleteRequest('Email/inbox/inTrash', {id: this.model.id}).then(() => {
            Espo.Ui.warning(this.translate('Retrieved from Trash', 'labels', 'Email'));
        });

        this.model.set('inTrash', false);

        if (this.model.collection) {
            this.model.collection.trigger('retrieving-from-trash', this.model.id);
        }
    }

    actionMoveToFolder() {
        this.createView('dialog', 'views/email-folder/modals/select-folder', {}, (view) => {
            view.render();

            this.listenToOnce(view, 'select', folderId => {
                this.clearView('dialog');

                Espo.Ajax.postRequest(`Email/inbox/folders/${folderId}`, {id: this.model.id})
                    .then(() => {
                        if (folderId === 'inbox') {
                            folderId = null;
                        }

                        this.model.set('folderId', folderId);

                        Espo.Ui.success(this.translate('Done'));
                    });
            });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionShowBodyPlain() {
        this.createView('bodyPlain', 'views/email/modals/body-plain', {
            model: this.model
        }, view => {
            view.render();
        });
    }

    handleAttachmentField() {
        if ((this.model.get('attachmentsIds') || []).length === 0) {
            this.hideField('attachments');
        } else {
            this.showField('attachments');
        }
    }

    handleCcField() {
        if (!this.model.get('cc')) {
            this.hideField('cc');
        } else {
            this.showField('cc');
        }
    }

    handleBccField() {
        if (!this.model.get('bcc')) {
            this.hideField('bcc');
        } else {
            this.showField('bcc');
        }
    }

    send() {
        var model = this.model;

        let status = model.get('status');

        model.set('status', 'Sending');

        this.isSending = true;

        var afterSend = () => {
            model.trigger('after:send');

            this.trigger('after:send');
            this.isSending = false;
        };

        this.once('after:save', afterSend, this);

        this.once('cancel:save', () => {
            this.off('after:save', afterSend);
            this.isSending = false;

            model.set('status', status);
        });

        this.once('before:save', () => {
            Espo.Ui.notify(this.translate('Sending...', 'labels', 'Email'));
        });

        return this.save();
    }

    // noinspection JSUnusedGlobalSymbols
    actionSendFromDetail() {
        this.setEditMode()
            .then(() => {
                return this.send();
            })
            .then(() => {
                this.setDetailMode();
            });
    }

    // noinspection JSUnusedGlobalSymbols
    exitAfterDelete() {
        var folderId = ((this.collection || {}).data || {}).folderId || null;

        if (folderId === 'inbox') {
            folderId = null;
        }

        var options = {
            isReturn: true,
            isReturnThroughLink: false,
            folder: folderId,
        };

        var url = '#' + this.scope;
        var action = null;

        if (folderId) {
            action = 'list';
            url += '/list/folder=' + folderId;
        }

        this.getRouter().dispatch(this.scope, action, options);
        this.getRouter().navigate(url, {trigger: false});

        return true;
    }

    // noinspection JSUnusedGlobalSymbols
    actionViewUsers(data) {
        var viewName =
            this.getMetadata()
                .get(['clientDefs', this.model.entityType, 'relationshipPanels', 'users', 'viewModalView']) ||
            this.getMetadata().get(['clientDefs', 'User', 'modalViews', 'relatedList']) ||
            'views/modals/related-list';

        var options = {
            model: this.model,
            link: 'users',
            scope: 'User',
            filtersDisabled: true,
            url: this.model.entityType + '/' + this.model.id + '/users',
            createDisabled: true,
            selectDisabled: !this.getUser().isAdmin(),
            rowActionsView: 'views/record/row-actions/relationship-view-and-unlink',
        };

        if (data.viewOptions) {
            for (var item in data.viewOptions) {
                options[item] = data.viewOptions[item];
            }
        }

        Espo.Ui.notify(' ... ');

        this.createView('modalRelatedList', viewName, options, (view) => {
            Espo.Ui.notify(false);

            view.render();

            this.listenTo(view, 'action', (event, element) => {
                Espo.Utils.handleAction(this, event, element);
            });

            this.listenToOnce(view, 'close', () => {
                this.clearView('modalRelatedList');
            });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionSend() {
        this.send()
            .then(() => {
                this.model.set('status', 'Sent');

                if (this.mode !== this.MODE_DETAIL) {
                    this.setDetailMode();
                    this.setFieldReadOnly('dateSent');
                    this.setFieldReadOnly('name');
                    this.setFieldReadOnly('attachments');
                    this.setFieldReadOnly('isHtml');
                    this.setFieldReadOnly('from');
                    this.setFieldReadOnly('to');
                    this.setFieldReadOnly('cc');
                    this.setFieldReadOnly('bcc');
                }
            });
    }

    // noinspection JSUnusedGlobalSymbols
    actionPrint() {
        /** @type {module:views/fields/wysiwyg} */
        let bodyView = this.getFieldView('body');

        if (!bodyView) {
            return;
        }

        let iframe = /** @type HTMLIFrameElement */bodyView.$el.find('iframe').get(0);

        if (iframe) {
            iframe.contentWindow.print();

            return;
        }

        let el = bodyView.$el.get(0);
        /** @type {Element} */
        let recordElement = this.$el.get(0);

        iframe = document.createElement('iframe');
        iframe.style.display = 'none';

        recordElement.append(iframe);

        let contentWindow = iframe.contentWindow;

        contentWindow.document.open();
        contentWindow.document.write(el.innerHTML);
        contentWindow.document.close();
        contentWindow.focus();
        contentWindow.print();
        contentWindow.onafterprint = () => {
            recordElement.removeChild(iframe);
        }
    }

    errorHandlerSendingFail(data) {
        if (!this.model.id) {
            this.model.id = data.id;
        }

        let msg = this.translate('sendingFailed', 'strings', 'Email');

        if (data.message) {
            let part = data.message;

            if (this.getLanguage().has(part, 'messages', 'Email')) {
                part = this.translate(part, 'messages', 'Email');
            }

            msg += ': ' + part;
        }

        Espo.Ui.error(msg, true);
        console.error(msg);
    }

    handleTasksField() {
        if ((this.model.get('tasksIds') || []).length === 0) {
            this.hideField('tasks');

            return;
        }

        this.showField('tasks');
    }

    /**
     * @protected
     * @param {JQueryKeyEventObject} e
     */
    handleShortcutKeyCtrlS(e) {
        if (this.inlineEditModeIsOn || this.buttonsDisabled) {
            return;
        }

        e.preventDefault();
        e.stopPropagation();

        if (this.mode !== this.MODE_EDIT) {
            return;
        }

        if (!this.saveAndContinueEditingAction) {
            return;
        }

        if (!this.hasAvailableActionItem('saveDraft') && !this.hasAvailableActionItem('saveAndContinueEditing')) {
            return;
        }

        this.actionSaveAndContinueEditing();
    }
}

export default EmailDetailRecordView;
PK]���f"views/email/record/detail-quick.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email/record/detail-quick', ['views/email/record/detail'], function (Dep) {

    return Dep.extend({

    	isWide: true,
        sideView: false,
    });
});
PK]����
#
#views/email/record/compose.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/email/record/compose */

import EditRecordView from 'views/record/edit';
import EmailDetailRecordView from 'views/email/record/detail';

class EmailComposeRecordView extends EditRecordView {

    isWide = true
    sideView = false

    setupBeforeFinal() {
        super.setupBeforeFinal();

        this.initialBody = null;
        this.initialIsHtml = null;

        if (!this.model.get('isHtml') && this.getPreferences().get('emailReplyForceHtml')) {
            let body = (this.model.get('body') || '').replace(/\n/g, '<br>');

            this.model.set('body', body, {silent: true});
            this.model.set('isHtml', true, {silent: true});
        }

        if (this.model.get('body')) {
            this.initialBody = this.model.get('body');
            this.initialIsHtml = this.model.get('isHtml');
        }

        if (!this.options.signatureDisabled && this.hasSignature()) {
            let addSignatureMethod = 'prependSignature';

            if (this.options.appendSignature) {
                addSignatureMethod = 'appendSignature';
            }

            let body = this[addSignatureMethod](this.model.get('body') || '', this.model.get('isHtml'));

            this.model.set('body', body, {silent: true});
        }
    }

    setup() {
        super.setup();

        this.isBodyChanged = false;

        this.listenTo(this.model, 'change:body', () => {
            this.isBodyChanged = true;
        });

        if (!this.options.removeAttachmentsOnSelectTemplate) {
            this.initialAttachmentsIds = this.model.get('attachmentsIds') || [];
            this.initialAttachmentsNames = this.model.get('attachmentsNames') || {};
        }

        this.initInsertTemplate();

        if (this.options.selectTemplateDisabled) {
            this.hideField('selectTemplate');
        }
    }

    initInsertTemplate() {
        this.listenTo(this.model, 'insert-template', data => {
            let body = this.model.get('body') || '';

            let bodyPlain = body.replace(/<br\s*\/?>/mg, '');

            bodyPlain = bodyPlain.replace(/<\/p\s*\/?>/mg, '');
            bodyPlain = bodyPlain.replace(/ /g, '');
            bodyPlain = bodyPlain.replace(/\n/g, '');

            let $div = $('<div>').html(bodyPlain);

            bodyPlain = $div.text();

            if (bodyPlain !== '' && this.isBodyChanged) {
                this.confirm({
                        message: this.translate('confirmInsertTemplate', 'messages', 'Email'),
                        confirmText: this.translate('Yes')
                    })
                    .then(() => this.insertTemplate(data));

                return;
            }

            this.insertTemplate(data);
        });
    }

    insertTemplate(data) {
        let body = data.body;

        if (this.hasSignature()) {
            body = this.appendSignature(body || '', data.isHtml);
        }

        if (this.initialBody && !this.isBodyChanged) {
            let initialBody = this.initialBody;

            if (data.isHtml !== this.initialIsHtml) {
                if (data.isHtml) {
                    initialBody = this.plainToHtml(initialBody);
                } else {
                    initialBody = this.htmlToPlain(initialBody);
                }
            }

            body += initialBody;
        }

        this.model.set('isHtml', data.isHtml);

        if (data.subject) {
            this.model.set('name', data.subject);
        }

        this.model.set('body', '');
        this.model.set('body', body);

        if (!this.options.removeAttachmentsOnSelectTemplate) {
            this.initialAttachmentsIds.forEach((id) => {
                if (data.attachmentsIds) {
                    data.attachmentsIds.push(id);
                }

                if (data.attachmentsNames) {
                    data.attachmentsNames[id] = this.initialAttachmentsNames[id] || id;
                }
            });
        }

        this.model.set({
            attachmentsIds: data.attachmentsIds,
            attachmentsNames: data.attachmentsNames
        });

        this.isBodyChanged = false;
    }

    prependSignature(body, isHtml) {
        if (isHtml) {
            let signature = this.getSignature();

            if (body) {
                signature += '';
            }

            return'<p><br></p>' + signature + body;
        }

        let signature = this.getPlainTextSignature();

        if (body) {
            signature += '\n';
        }

        return '\n\n' + signature + body;
    }

    appendSignature(body, isHtml) {
        if (isHtml) {
            let signature = this.getSignature();

            return  body + '' + signature;
        }

        let signature = this.getPlainTextSignature();

        return body + '\n\n' + signature;
    }

    hasSignature() {
        return !!this.getPreferences().get('signature');
    }

    getSignature() {
        return this.getPreferences().get('signature') || '';
    }

    getPlainTextSignature() {
        let value = this.getSignature().replace(/<br\s*\/?>/mg, '\n');

        value = $('<div>').html(value).text();

        return value;
    }

    afterSave() {
        super.afterRender();

        if (this.isSending && this.model.get('status') === 'Sent') {
            Espo.Ui.success(this.translate('emailSent', 'messages', 'Email'));
        }
    }

    send() {
        EmailDetailRecordView.prototype.send.call(this);
    }

    saveDraft(options) {
        let model = this.model;

        model.set('status', 'Draft');

        let subjectView = this.getFieldView('subject');

        if (subjectView) {
            subjectView.fetchToModel();

            if (!model.get('name')) {
                model.set('name', this.translate('No Subject', 'labels', 'Email'));
            }
        }

        return this.save(options);
    }

    htmlToPlain(text) {
        text = text || '';

        let value = text.replace(/<br\s*\/?>/mg, '\n');

        value = value.replace(/<\/p\s*\/?>/mg, '\n\n');

        let $div = $('<div>').html(value);

        $div.find('style').remove();
        $div.find('link[ref="stylesheet"]').remove();

        value =  $div.text();

        return value;
    }

    plainToHtml(html) {
        html = html || '';

        return html.replace(/\n/g, '<br>');
    }

    // noinspection JSUnusedGlobalSymbols
    errorHandlerSendingFail(data) {
        EmailDetailRecordView.prototype.errorHandlerSendingFail.call(this, data);
    }

    focusForCreate() {
        if (!this.model.get('to')) {
            this.$el
                .find('.field[data-name="to"] input')
                .focus();

            return;
        }

        if (!this.model.get('subject')) {
            this.$el
                .find('.field[data-name="subject"] input')
                .focus();

            return;
        }

        if (this.model.get('isHtml')) {
            let $div = this.$el.find('.field[data-name="body"] .note-editable');

            if (!$div.length) {
                return;
            }

            $div.focus();

            return;
        }

        this.$el
            .find('.field[data-name="body"] textarea')
            .prop('selectionEnd', 0)
            .focus();
    }
}

export default EmailComposeRecordView;
PK]K�Lpe7e7views/email/record/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/email/record/list */

import ListRecordView from 'views/record/list';
import MassActionHelper from 'helpers/mass-action';

class EmailListRecordView extends ListRecordView {

    rowActionsView = 'views/email/record/row-actions/default'

    massActionList = ['remove', 'massUpdate']

    setup() {
        super.setup();

        if (this.collection.url === this.entityType) {
            this.addMassAction('retrieveFromTrash', false, true);
            this.addMassAction('moveToFolder', true, true);
            this.addMassAction('markAsNotImportant', false, true);
            this.addMassAction('markAsImportant', false, true);
            this.addMassAction('markAsNotRead', false, true);
            this.addMassAction('markAsRead', false, true);
            this.addMassAction('moveToTrash', false, true);

            this.dropdownItemList.push({
                name: 'markAllAsRead',
                label: 'Mark all as read',
            });
        }

        this.listenTo(this.collection, 'moving-to-trash', (id) => {
            let model = this.collection.get(id);

            if (model) {
                model.set('inTrash', true);
            }

            if (this.collection.selectedFolderId !== 'trash' && this.collection.selectedFolderId !== 'all') {
                this.removeRecordFromList(id);
            }
        });

        this.listenTo(this.collection, 'retrieving-from-trash', (id) => {
            let model = this.collection.get(id);

            if (model) {
                model.set('inTrash', false);
            }

            if (this.collection.selectedFolderId === 'trash') {
                this.removeRecordFromList(id);
            }
        });
    }

    // noinspection JSUnusedGlobalSymbols
    massActionMarkAsRead() {
        let ids = [];

        for (let i in this.checkedList) {
            ids.push(this.checkedList[i]);
        }

        Espo.Ajax.postRequest('Email/inbox/read', {ids: ids});

        ids.forEach(id => {
            let model = this.collection.get(id);

            if (model) {
                model.set('isRead', true);
            }
        });
    }

    // noinspection JSUnusedGlobalSymbols
    massActionMarkAsNotRead() {
        let ids = [];

        for (let i in this.checkedList) {
            ids.push(this.checkedList[i]);
        }

        Espo.Ajax.deleteRequest('Email/inbox/read', {ids: ids});

        ids.forEach(id => {
            let model = this.collection.get(id);

            if (model) {
                model.set('isRead', false);
            }
        });
    }

    massActionMarkAsImportant() {
        let ids = [];

        for (let i in this.checkedList) {
            ids.push(this.checkedList[i]);
        }

        Espo.Ajax.postRequest('Email/inbox/important', {ids: ids});

        ids.forEach(id => {
            let model = this.collection.get(id);

            if (model) {
                model.set('isImportant', true);
            }
        });
    }

    massActionMarkAsNotImportant() {
        let ids = [];

        for (let i in this.checkedList) {
            ids.push(this.checkedList[i]);
        }

        Espo.Ajax.deleteRequest('Email/inbox/important', {ids: ids});

        ids.forEach(id => {
            let model = this.collection.get(id);

            if (model) {
                model.set('isImportant', false);
            }
        });
    }

    // noinspection JSUnusedGlobalSymbols
    massActionMoveToTrash() {
        let ids = [];

        for (let i in this.checkedList) {
            ids.push(this.checkedList[i]);
        }

        Espo.Ajax
            .postRequest('Email/inbox/inTrash', {ids: ids})
            .then(() => {
                Espo.Ui.warning(this.translate('Moved to Trash', 'labels', 'Email'));
            });

        if (this.collection.selectedFolderId === 'trash') {
            return;
        }

        ids.forEach(id => {
            this.collection.trigger('moving-to-trash', id, this.collection.get(id));

            this.uncheckRecord(id, null, true);
        });
    }

    // noinspection JSUnusedGlobalSymbols
    massActionRetrieveFromTrash() {
        let ids = [];

        for (let i in this.checkedList) {
            ids.push(this.checkedList[i]);
        }

        Espo.Ajax
            .deleteRequest('Email/inbox/inTrash', {ids: ids})
            .then(() => {
                Espo.Ui.success(this.translate('Done'));
            });

        if (this.collection.selectedFolderId !== 'trash') {
            return;
        }

        ids.forEach(id => {
            this.collection.trigger('retrieving-from-trash', id, this.collection.get(id));

            this.uncheckRecord(id, null, true);
        });
    }

    massMoveToFolder(folderId) {
        let params = this.getMassActionSelectionPostData();
        let helper = new MassActionHelper(this);
        let idle = !!params.searchParams && helper.checkIsIdle();

        Espo.Ui.notify(this.translate('pleaseWait', 'messages'));

        Espo.Ajax
            .postRequest('MassAction', {
                entityType: this.entityType,
                action: 'moveToFolder',
                params: params,
                idle: idle,
                data: {
                    folderId: folderId,
                },
            })
            .then(result => {
                Espo.Ui.notify(false);

                if (result.id) {
                    helper
                        .process(result.id, 'moveToFolder')
                        .then(view => {
                            this.listenToOnce(view, 'close:success', () => {
                                this.collection.fetch().then(() => {
                                    Espo.Ui.success(this.translate('Done'));
                                });
                            });
                        });

                    return;
                }

                this.collection.fetch().then(() => {
                    Espo.Ui.success(this.translate('Done'));
                });
            });
    }

    // noinspection JSUnusedGlobalSymbols
    massActionMoveToFolder() {
        this.createView('dialog', 'views/email-folder/modals/select-folder', {
            headerText: this.translate('Move to Folder', 'labels', 'Email'),
        }, view => {
            view.render();

            this.listenToOnce(view, 'select', folderId => {
                this.clearView('dialog');

                this.massMoveToFolder(folderId);
            });
        });
    }

    actionMarkAsImportant(data) {
        data = data || {};

        let id = data.id;

        Espo.Ajax.postRequest('Email/inbox/important', {id: id});

        let model = this.collection.get(id);

        if (model) {
            model.set('isImportant', true);
        }
    }

    actionMarkAsNotImportant(data) {
        data = data || {};

        let id = data.id;

        Espo.Ajax.deleteRequest('Email/inbox/important', {id: id});

        let model = this.collection.get(id);

        if (model) {
            model.set('isImportant', false);
        }
    }

    // noinspection JSUnusedGlobalSymbols
    actionMarkAllAsRead() {
        Espo.Ajax.postRequest('Email/inbox/read', {all: true});

        this.collection.forEach(model => {
            model.set('isRead', true);
        });

        this.collection.trigger('all-marked-read');
    }

    actionMoveToTrash(data) {
        let id = data.id;

        Espo.Ui.notify(' ... ');

        Espo.Ajax
            .postRequest('Email/inbox/inTrash', {id: id})
            .then(() => {
                Espo.Ui.warning(this.translate('Moved to Trash', 'labels', 'Email'));

                this.collection.trigger('moving-to-trash', id, this.collection.get(id));
            });
    }

    // noinspection JSUnusedGlobalSymbols
    actionRetrieveFromTrash(data) {
        let id = data.id;

        Espo.Ui.notify(' ... ');

        this.retrieveFromTrash(id)
            .then(() => {
                Espo.Ui.warning(this.translate('Retrieved from Trash', 'labels', 'Email'));

                this.collection.trigger('retrieving-from-trash', id, this.collection.get(id));
            });
    }

    /**
     * @param {string} id
     * @return {Promise}
     */
    retrieveFromTrash(id) {
        return Espo.Ajax.deleteRequest('Email/inbox/inTrash', {id: id});
    }

    massRetrieveFromTrashMoveToFolder(folderId) {
        let ids = [];

        for (let i in this.checkedList) {
            ids.push(this.checkedList[i]);
        }

        Espo.Ajax
            .deleteRequest('Email/inbox/inTrash', {ids: ids})
            .then(() => {
                ids.forEach(id => {
                    this.collection.trigger('retrieving-from-trash', id, this.collection.get(id));
                });

                return Espo.Ajax
                    .postRequest(`Email/inbox/folders/${folderId}`, {ids: ids})
                    .then(() => {
                        Espo.Ui.success(this.translate('Done'));
                    })
            });
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * @todo Use one API request.
     */
    actionRetrieveFromTrashMoveToFolder(data) {
        let id = data.id;
        let folderId = data.folderId;

        Espo.Ui.notify(' ... ');

        this.retrieveFromTrash(id)
            .then(() => {
                return this.moveToFolder(id, folderId)
            })
            .then(() => {
                this.collection.fetch().then(() => {
                    Espo.Ui.success(this.translate('Done'));
                });
            });
    }

    /**
     * @param {string} id
     * @param {string} folderId
     * @return {Promise}
     */
    moveToFolder(id, folderId) {
        return Espo.Ajax.postRequest(`Email/inbox/folders/${folderId}`, {id: id});
    }

    actionMoveToFolder(data) {
        let id = data.id;
        let folderId = data.folderId;

        if (folderId) {
            Espo.Ui.notify(' ... ');

            this.moveToFolder(id, folderId)
                .then(() => {
                    this.collection.fetch().then(() => {
                        Espo.Ui.success(this.translate('Done'));
                    });
                });

            return;
        }

        this.createView('dialog', 'views/email-folder/modals/select-folder', {
            headerText: this.translate('Move to Folder', 'labels', 'Email'),
        }, view => {
            view.render();

            this.listenToOnce(view, 'select', folderId => {
                this.clearView('dialog');

                Espo.Ui.notify(' ... ');

                this.moveToFolder(id, folderId)
                    .then(() => {
                        this.collection.fetch().then(() => {
                            Espo.Ui.success(this.translate('Done'));
                        });
                    });
            });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionSend(data) {
        let id = data.id;

        this.confirm({
            message: this.translate('sendConfirm', 'messages', 'Email'),
            confirmText: this.translate('Send', 'labels', 'Email'),
        }).then(() => {
            let model = this.collection.get(id);

            if (!model) {
                return;
            }

            Espo.Ui.notify(this.translate('Sending...', 'labels', 'Email'));

            model
                .save({
                    status: 'Sending',
                })
                .then(() => {
                    Espo.Ui.success(this.translate('emailSent', 'messages', 'Email'));

                    if (this.collection.selectedFolderId === 'drafts') {
                        this.removeRecordFromList(id);
                        this.uncheckRecord(id, null, true);
                        this.collection.trigger('draft-sent');
                    }
                }
            );
        });
    }

    // noinspection JSUnusedGlobalSymbols
    toggleMassMarkAsImportant() {
        let allImportant = !this.checkedList
            .map(id => this.collection.get(id))
            .find(m => !m.get('isImportant'));

        if (allImportant) {
            this.massActionMarkAsNotImportant();

            return;
        }

        this.massActionMarkAsImportant();
    }
}

export default EmailListRecordView;
PK]���P��!views/email/record/detail-side.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email/record/detail-side', ['views/record/detail-side'], function (Dep) {

    return Dep.extend({});
});
PK]Wup���#views/email/record/list-expanded.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define(
    'views/email/record/list-expanded',
    ['views/record/list-expanded', 'views/email/record/list'],
    function (Dep, List) {

    return Dep.extend({

        actionMarkAsImportant: function (data) {
            List.prototype.actionMarkAsImportant.call(this, data);
        },

        actionMarkAsNotImportant: function (data) {
            List.prototype.actionMarkAsNotImportant.call(this, data);
        },

        actionMoveToTrash: function (data) {
            List.prototype.actionMoveToTrash.call(this, data);
        },

    });
});
PK]"ed		 views/email/modals/body-plain.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email/modals/body-plain', ['views/modal'], function (Dep) {

    return Dep.extend({

        backdrop: true,

        templateContent: '<div class="field" data-name="body-plain">{{{bodyPlain}}}</div>',

        setup: function () {
            Dep.prototype.setup.call(this);

            this.buttonList.push({
                'name': 'cancel',
                'label': 'Close'
            });

            this.headerText = this.model.get('name');

            this.createView('bodyPlain', 'views/fields/text', {
                selector: '.field[data-name="bodyPlain"]',
                model: this.model,
                defs: {
                    name: 'bodyPlain',
                    params: {
                        readOnly: true,
                        inlineEditDisabled: true,
                    },
                },
            });
        },
    });
});
PK] -��
�
views/email/modals/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email/modals/detail', ['views/modals/detail', 'views/email/detail'], function (Dep, Detail) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            this.addButton({
                name: 'reply',
                label: 'Reply',
                hidden: this.model && this.model.get('status') === 'Draft',
                style: 'danger',
                position: 'right',
            }, true)

            if (this.model) {
                this.listenToOnce(this.model, 'sync', () => {
                    setTimeout(() => {
                        this.model.set('isRead', true);
                    }, 50);
                });
            }
        },

        controlRecordButtonsVisibility: function () {
            Dep.prototype.controlRecordButtonsVisibility.call(this);

            if (this.model.get('status') === 'Draft' || !this.getAcl().check('Email', 'create')) {
                this.hideActionItem('reply');

                return;
            }

            this.showActionItem('reply');
        },

        actionReply: function (data, e) {
            Detail.prototype.actionReply.call(this, {}, e, this.getPreferences().get('emailReplyToAllByDefault'));
        },
    });
});
PK]���3<<"views/email/modals/insert-field.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email/modals/insert-field',
['views/modal', 'helpers/misc/field-language'], function (Dep, FieldLanguage) {

    return Dep.extend({

        backdrop: true,

        templateContent: `
            {{#each viewObject.dataList}}
                <div class="margin-bottom">
                <h5>{{label}}: {{translate entityType category='scopeNames'}}</h5>
                </div>
                <ul class="list-group no-side-margin">
                    {{#each dataList}}
                    <li class="list-group-item clearfix">
                        <a role="button"
                            data-action="insert" class="text-bold" data-name="{{name}}" data-type="{{../type}}">
                            {{label}}
                        </a>

                        <div class="pull-right"
                            style="width: 50%; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;">
                            {{valuePreview}}
                        </div>
                    </li>
                    {{/each}}
                </ul>
            {{/each}}

            {{#unless viewObject.dataList.length}}
                {{translate 'No Data'}}
            {{/unless}}
        `,

        events: {
            'click [data-action="insert"]': function (e) {
                let name = $(e.currentTarget).data('name');
                let type = $(e.currentTarget).data('type');

                this.insert(type, name);
            },
        },

        setup: function () {
            Dep.prototype.setup.call(this);

            this.headerText = this.translate('Insert Field', 'labels', 'Email');

            this.fieldLanguage = new FieldLanguage(this.getMetadata(), this.getLanguage());

            this.wait(
                Espo.Ajax
                    .getRequest('Email/insertFieldData', {
                        parentId: this.options.parentId,
                        parentType: this.options.parentType,
                        to: this.options.to,
                    })
                    .then(fetchedData => {
                        this.fetchedData = fetchedData;
                        this.prepareData();
                    })
            );
        },

        prepareData: function () {
            this.dataList = [];

            var fetchedData = this.fetchedData;
            var typeList = ['parent', 'to'];

            typeList.forEach(type => {
                if (!fetchedData[type]) {
                    return;
                }

                let entityType = fetchedData[type].entityType;
                let id = fetchedData[type].id;

                for (let it of this.dataList) {
                    if (it.id === id && it.entityType === entityType) {
                        return;
                    }
                }

                var dataList = this.prepareDisplayValueList(fetchedData[type].entityType, fetchedData[type].values);

                if (!dataList.length) {
                    return;
                }

                this.dataList.push({
                    type: type,
                    entityType: entityType,
                    id: id,
                    name: fetchedData[type].name,
                    dataList: dataList,
                    label: this.translate(type, 'fields', 'Email'),
                });
            });
        },

        prepareDisplayValueList: function (scope, values) {
            let list = [];

            let attributeList = Object.keys(values);
            let labels = {};

            attributeList.forEach(item => {
                labels[item] = this.fieldLanguage.translateAttribute(scope, item);
            });

            attributeList = attributeList
                .sort((v1, v2) => {
                    return labels[v1].localeCompare(labels[v2]);
                });

            let ignoreAttributeList = ['id', 'modifiedAt', 'modifiedByName'];

            let fm = this.getFieldManager();

            fm.getEntityTypeFieldList(scope).forEach(field => {
                let type = this.getMetadata().get(['entityDefs', scope, 'fields', field, 'type']);

                if (~['link', 'linkOne', 'image', 'filed', 'linkParent'].indexOf(type)) {
                    ignoreAttributeList.push(field + 'Id');
                }

                if (type === 'linkParent') {
                    ignoreAttributeList.push(field + 'Type');
                }
            });

            attributeList.forEach(item => {
                if (~ignoreAttributeList.indexOf(item)) {
                    return;
                }

                let value = values[item];

                if (value === null || value === '') {
                    return;
                }

                if (typeof value == 'boolean') {
                    return;
                }

                if (Array.isArray(value)) {
                    for (let v in value) {
                        if (typeof v  !== 'string') {
                            return;
                        }
                    }

                    value = value.split(', ');
                }

                value = this.getHelper().sanitizeHtml(value);

                var valuePreview = value.replace(/<br( \/)?>/gm, ' ');

                value = value.replace(/(?:\r\n|\r|\n)/g, '');
                value = value.replace(/<br( \/)?>/gm, '\n');

                list.push({
                    name: item,
                    label: labels[item],
                    value: value,
                    valuePreview: valuePreview,
                });
            });

            return list;
        },

        insert: function (type, name) {
            for (let g of this.dataList) {
                if (g.type !== type) {
                    continue;
                }

                for (let i of g.dataList) {
                    if (i.name !== name) {
                        continue;
                    }

                    this.trigger('insert', i.value);

                    break;
                }

                break;
            }

            this.close();
        },
    });
});
PK]*p�!views/email/modals/attachments.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email/modals/attachments', ['views/modal'], function (Dep) {

    /**
     * @class
     * @name Class
     * @extends module:views/modal
     * @memberOf module:views/email/modals/attachments
     */
    return Dep.extend(/** @lends module:views/email/modals/attachments.Class# */{

        backdrop: true,

        templateContent: `<div class="record">{{{record}}}</div>`,

        setup: function () {
            Dep.prototype.setup.call(this);

            this.headerText = this.translate('attachments', 'fields', 'Email');

            this.createView('record', 'views/record/detail', {
                model: this.model,
                selector: '.record',
                readOnly: true,
                sideView: null,
                buttonsDisabled: true,
                detailLayout: [
                    {
                        rows: [
                            [
                                {
                                    name: 'attachments',
                                    noLabel: true,
                                },
                                false,
                            ]
                        ]
                    }
                ],
            });

            if (!this.model.has('attachmentsIds')) {
                this.wait(
                    this.model.fetch()
                );
            }
        },
    });
});
PK]����N�Nviews/email/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ListView from 'views/list';

class EmailListView extends ListView {

    createButton = false
    template = 'email/list'
    folderId = null
    folderScope = 'EmailFolder'
    selectedFolderId = null
    defaultFolderId = 'inbox'
    keepCurrentRootUrl = true
    stickableTop = null

    /** @const */
    FOLDER_ALL = 'all'
    /** @const */
    FOLDER_INBOX = 'inbox'
    /** @const */
    FOLDER_IMPORTANT = 'important'
    /** @const */
    FOLDER_SENT = 'sent'
    /** @const */
    FOLDER_DRAFTS = 'drafts'
    /** @const */
    FOLDER_TRASH = 'trash'

    noDropFolderIdList = [
        'sent',
        'drafts',
    ]

    /** @inheritDoc */
    createListRecordView(fetch) {
        return super.createListRecordView(fetch)
            .then(view => {
                this.listenTo(view, 'after:render', () => this.initDraggable(null));
                this.listenTo(view, 'after:show-more', fromIndex => this.initDraggable(fromIndex));
            });
    }

    /**
     * @private
     */
    initDroppable() {
        // noinspection JSUnresolvedReference
        this.$el.find('.folders-container .folder-list > .droppable')
            .droppable({
                accept: '.list-row',
                tolerance: 'pointer',
                over: (e) => {
                    if (!this.isDroppable(e)) {
                        return;
                    }

                    let $target = $(e.target);

                    $target.removeClass('success');
                    $target.addClass('active');
                    $target.find('a').css('pointer-events', 'none');
                },
                out: (e) => {
                    if (!this.isDroppable(e)) {
                        return;
                    }

                    let $target = $(e.target);

                    $target.removeClass('active');
                    $target.find('a').css('pointer-events', '');
                },
                drop: (e, ui) => {
                    if (!this.isDroppable(e)) {
                        return;
                    }

                    let $target = $(e.target);
                    let $helper = $(ui.helper);

                    $target.find('a').css('pointer-events', '');

                    let folderId = $target.attr('data-id');

                    let id = $helper.attr('data-id');
                    id = id === '' ? true : id;

                    this.onDrop(folderId, id);

                    $target.removeClass('active');
                    $target.addClass('success');

                    setTimeout(() => {
                        $target.removeClass('success');
                    }, 1000);
                },
            });
    }

    /**
     * @private
     * @param {?Number} fromIndex
     */
    initDraggable(fromIndex) {
        fromIndex = fromIndex || 0;

        let isTouchDevice =  ('ontouchstart' in window) || navigator.maxTouchPoints > 0;

        if (isTouchDevice) {
            return;
        }

        let $container = this.$el.find('.list-container > .list');

        const recordView = this.getEmailRecordView();

        this.collection.models.slice(fromIndex).forEach(m => {
            let $row = $container.find(`.list-row[data-id="${m.id}"]`).first();

            // noinspection JSUnresolvedReference
            $row.draggable({
                cancel: 'input,textarea,button,select,option,.dropdown-menu',
                helper: () => {
                    let text = this.translate('Moving to Folder', 'labels', 'Email');

                    if (
                        recordView.isIdChecked(m.id) &&
                        !recordView.allResultIsChecked &&
                        recordView.checkedList.length > 1
                    ) {
                        text += ' · ' + recordView.checkedList.length;
                    }

                    let draggedId = m.id;

                    if (
                        recordView.isIdChecked(m.id) &&
                        !recordView.allResultIsChecked
                    ) {
                        draggedId = '';
                    }

                    return $('<div>')
                        .attr('data-id', draggedId)
                        .css('cursor', 'grabbing')
                        .addClass('draggable-helper')
                        .text(text);
                },
                distance: 8,
                containment: this.$el,
                appendTo: 'body',
                cursor: 'grabbing',
                cursorAt: {
                    top: 0,
                    left: 0,
                },
                start: (e) => {
                    let $target = $(e.target);

                    $target.closest('tr').addClass('active');
                },
                stop: () => {
                    if (!recordView.isIdChecked(m.id)) {
                        $container.find(`.list-row[data-id="${m.id}"]`).first().removeClass('active');
                    }
                },
            });
        });
    }

    isDroppable(e) {
        let $target = $(e.target);
        let folderId = $target.attr('data-id');

        if (this.selectedFolderId === this.FOLDER_DRAFTS) {
            return false;
        }

        if (this.selectedFolderId === this.FOLDER_SENT && folderId === this.FOLDER_INBOX) {
            return false;
        }

        if (this.selectedFolderId === this.FOLDER_ALL) {
            if (folderId.indexOf('group:') === 0) {
                return true;
            }

            return false;
        }

        if (folderId === this.FOLDER_ALL) {
            if (this.selectedFolderId.indexOf('group:') === 0) {
                return true;
            }

            return false;
        }

        if (this.selectedFolderId === this.FOLDER_DRAFTS) {
            if (folderId.indexOf('group:') === 0) {
                return true;
            }

            if (folderId === this.FOLDER_TRASH) {
                return false;
            }

            return true;
        }

        return true;
    }

    setup() {
        super.setup();

        this.addMenuItem('dropdown', false);

        if (this.getAcl().checkScope('EmailAccountScope')) {
            this.addMenuItem('dropdown', {
                name: 'reply',
                label: 'Email Accounts',
                link: '#EmailAccount/list/userId=' + this.getUser().id + '&userName=' +
                    encodeURIComponent(this.getUser().get('name'))
            });
        }

        if (this.getUser().isAdmin()) {
            this.addMenuItem('dropdown', {
                link: '#InboundEmail',
                label: 'Inbound Emails'
            });
        }

        this.foldersDisabled = this.foldersDisabled ||
            this.getConfig().get('emailFoldersDisabled') ||
            this.getMetadata().get(['scopes', this.folderScope, 'disabled']) ||
            !this.getAcl().checkScope(this.folderScope);

        let params = this.options.params || {};

        this.selectedFolderId = params.folder || this.defaultFolderId;

        if (this.foldersDisabled) {
            this.selectedFolderId = null;
        }

        this.applyFolder();

        this.initEmailShortcuts();

        this.on('remove', () => {
            $(window).off('resize.email-folders');
            $(window).off('scroll.email-folders');
        });
    }

    data() {
        let data = {};
        data.foldersDisabled = this.foldersDisabled;

        return data;
    }

    /** @inheritDoc */
    createSearchView() {
        /** @type {Promise<module:view>} */
        let promise = super.createSearchView();

        promise.then(view => {
            this.listenTo(view, 'update-ui', () => {
                this.stickableTop = null;

                setTimeout(() => {
                    $(window).trigger('scroll')

                    // If search fields are not yet rendered, the value may be wrong.
                    this.stickableTop = null;
                }, 100);
            });
        });

        return promise;
    }

    initEmailShortcuts() {
        this.shortcutKeys['Control+Delete'] = e => {
            if (!this.hasSelectedRecords()) {
                return;
            }

            e.preventDefault();
            e.stopPropagation();

            this.getEmailRecordView().massActionMoveToTrash();
        };

        this.shortcutKeys['Control+KeyI'] = e => {
            if (!this.hasSelectedRecords()) {
                return;
            }

            e.preventDefault();
            e.stopPropagation();

            this.getEmailRecordView().toggleMassMarkAsImportant();
        };

        this.shortcutKeys['Control+KeyM'] = e => {
            if (!this.hasSelectedRecords()) {
                return;
            }

            e.preventDefault();
            e.stopPropagation();

            this.getEmailRecordView().massActionMoveToFolder();
        };
    }

    hasSelectedRecords() {
        let recordView = this.getEmailRecordView();

        return recordView.checkedList &&
            recordView.checkedList.length &&
            !recordView.allResultIsChecked;
    }

    /** @inheritDoc */
    setupReuse(params) {
        this.applyRoutingParams(params);
        this.initDroppable();
        this.initStickableFolders();
    }

    /**
     * @param {Object.<string,*>} [data]
     */
    actionComposeEmail(data) {
        data = data || {};

        Espo.Ui.notify(' ... ');

        let viewName = this.getMetadata().get('clientDefs.Email.modalViews.compose') ||
            'views/modals/compose-email';

        let options = {
            attributes: {
                status: 'Draft',
            },
            focusForCreate: data.focusForCreate,
        };

        this.createView('quickCreate', viewName, options, (view) => {
            view.render();
            view.notify(false);

            this.listenToOnce(view, 'after:save', () => {
                this.collection.fetch();
            });
        });
    }

    afterRender() {
        super.afterRender();

        if (!this.foldersDisabled && !this.hasView('folders')) {
            this.loadFolders();
        }
    }

    getFolderCollection(callback) {
        this.getCollectionFactory().create(this.folderScope, (collection) => {
            collection.url = 'EmailFolder/action/listAll';
            collection.maxSize = 200;

            this.listenToOnce(collection, 'sync', () =>{
                callback.call(this, collection);
            });

            collection.fetch();
        });
    }

    loadFolders() {
        var xhr = null;

        let auxFolderList = [
            this.FOLDER_TRASH,
            this.FOLDER_DRAFTS,
            this.FOLDER_ALL,
            this.FOLDER_INBOX,
            this.FOLDER_IMPORTANT,
            this.FOLDER_SENT,
        ];

        this.getFolderCollection(collection => {
            collection.forEach(model => {
                if (this.noDropFolderIdList.indexOf(model.id) === -1) {
                    model.droppable = true;
                }

                if (model.id.indexOf('group:') === 0) {
                    model.title = this.translate('groupFolder', 'fields', 'Email');
                }
                else if (auxFolderList.indexOf(model.id) === -1) {
                    model.title = this.translate('folder', 'fields', 'Email');
                }
            });

            this.createView('folders', 'views/email-folder/list-side', {
                collection: collection,
                emailCollection: this.collection,
                selector: '.folders-container',
                showEditLink: this.getAcl().check(this.folderScope, 'edit'),
                selectedFolderId: this.selectedFolderId,
            }, view => {
                view.render()
                    .then(() => this.initDroppable())
                    .then(() => this.initStickableFolders());

                this.listenTo(view, 'select', (id) => {
                    this.selectedFolderId = id;
                    this.applyFolder();

                    if (xhr && xhr.readyState < 4) {
                        xhr.abort();
                    }

                    Espo.Ui.notify(' ... ');

                    xhr = this.collection
                        .fetch()
                        .then(() => Espo.Ui.notify(false));

                    if (id !== this.defaultFolderId) {
                        this.getRouter().navigate('#Email/list/folder=' + id);
                    } else {
                        this.getRouter().navigate('#Email');
                    }

                    this.updateLastUrl();
                });
            });
        });
    }

    applyFolder() {
        this.collection.selectedFolderId = this.selectedFolderId;

        if (!this.selectedFolderId) {
            this.collection.whereFunction = null;

            return;
        }

        this.collection.whereFunction = () => {
            return [
                {
                    type: 'inFolder',
                    attribute: 'folderId',
                    value: this.selectedFolderId,
                }
            ];
        };
    }

    /**
     * @protected
     * @return {module:views/email-folder/list-side}
     */
    getFoldersView() {
        return this.getView('folders')
    }

    applyRoutingParams(params) {
        let id;

        if ('folder' in params) {
            id = params.folder || 'inbox';
        } else {
            return;
        }

        if (!params.isReturnThroughLink && id !== this.selectedFolderId) {
            var foldersView = this.getFoldersView();

            if (foldersView) {
                foldersView.actionSelectFolder(id);
                foldersView.reRender();
                $(window).scrollTop(0);
            }
        }
    }

    onDrop(folderId, id) {
        let recordView = this.getEmailRecordView();

        if (folderId === this.FOLDER_IMPORTANT) {
            setTimeout(() => {
                id === true ?
                    recordView.massActionMarkAsImportant() :
                    recordView.actionMarkAsImportant({id: id});
            }, 10);

            return;
        }

        if (this.selectedFolderId === this.FOLDER_TRASH) {
            if (folderId === this.FOLDER_TRASH) {
                return;
            }

            id === true ?
                recordView.massRetrieveFromTrashMoveToFolder(folderId) :
                recordView.actionRetrieveFromTrashMoveToFolder({id: id, folderId: folderId});

            return;
        }

        if (folderId === this.FOLDER_TRASH) {
            id === true ?
                recordView.massActionMoveToTrash() :
                recordView.actionMoveToTrash({id: id});

            return;
        }

        if (this.selectedFolderId.indexOf('group:') === 0 && folderId === this.FOLDER_ALL) {
            folderId = this.FOLDER_INBOX;
        }

        id === true ?
            recordView.massMoveToFolder(folderId) :
            recordView.actionMoveToFolder({id: id, folderId: folderId});
    }

    /**
     * @protected
     * @return {module:views/email/record/list}
     */
    getEmailRecordView() {
        return /** @type {module:views/email/record/list} */this.getRecordView();
    }

    /**
     * @private
     */
    initStickableFolders() {
        let $window = $(window);
        let $list = this.$el.find('.list-container');
        let $container = this.$el.find('.folders-container');
        let $left = this.$el.find('.left-container').first();

        let screenWidthXs = this.getThemeManager().getParam('screenWidthXs');
        let isSmallScreen = $(window.document).width() < screenWidthXs;
        let offset = this.getThemeManager().getParam('navbarHeight') +
            (this.getThemeManager().getParam('buttonsContainerHeight') || 47);

        let bottomSpaceHeight = parseInt(window.getComputedStyle($('#content').get(0)).paddingBottom, 10);

        let getOffsetTop = (/** JQuery */$element) => {
            let element = /** @type {HTMLElement} */$element.get(0);

            let value = 0;

            while (element) {
                value += !isNaN(element.offsetTop) ? element.offsetTop : 0;

                element = element.offsetParent;
            }

            if (isSmallScreen) {
                return value;
            }

            return value - offset;
        };

        this.stickableTop = getOffsetTop($list);

        let control = () => {
            let start = this.stickableTop;

            if (start === null) {
                start = this.stickableTop = getOffsetTop($list);
            }

            let scrollTop = $window.scrollTop();

            if (scrollTop <= start || isSmallScreen) {
                $container
                    .removeClass('sticked')
                    .width('')
                    .scrollTop(0);

                $container.css({
                    maxHeight: '',
                });

                return;
            }

            if (scrollTop > start) {
                let scroll = $window.scrollTop() - start;

                $container
                    .addClass('sticked')
                    .width($left.outerWidth(true))
                    .scrollTop(scroll);

                let topStickPosition = parseInt(window.getComputedStyle($container.get(0)).top);

                let maxHeight = $window.height() - topStickPosition - bottomSpaceHeight;

                $container.css({maxHeight: maxHeight});
            }
        };

        $window.on('resize.email-folders', () => control());
        $window.on('scroll.email-folders', () => control());
    }

    /**
     * @protected
     * @param {JQueryKeyEventObject} e
     */
    handleShortcutKeyCtrlSpace(e) {
        if (e.target.tagName === 'TEXTAREA' || e.target.tagName === 'INPUT') {
            return;
        }

        if (!this.getAcl().checkScope(this.scope, 'create')) {
            return;
        }

        e.preventDefault();
        e.stopPropagation();

        this.actionComposeEmail({focusForCreate: true});
    }
}

export default EmailListView;
PK]�j���!views/notification/fields/read.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import BaseFieldView from 'views/fields/base';

class NotificationReadFieldView extends BaseFieldView {

    type = 'read'
    listTemplate = 'notification/fields/read'
    detailTemplate = 'notification/fields/read'

    data() {
        return {
            isRead: this.model.get('read'),
        };
    }
}

export default NotificationReadFieldView;
PK]�#f��+views/notification/fields/read-with-menu.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import BaseFieldView from 'views/fields/base';

class NotificationReadWithMenuFieldView extends BaseFieldView {

    type = 'read'
    listTemplate = 'notification/fields/read-with-menu'
    detailTemplate = 'notification/fields/read-with-menu'

    data() {
        return {
            isRead: this.model.get('read'),
        };
    }
}

export default NotificationReadWithMenuFieldView;
PK]�ua8��&views/notification/fields/container.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import BaseFieldView from 'views/fields/base';

class NotificationContainerFieldView extends BaseFieldView {

    type = 'notification'

    listTemplate = 'notification/fields/container'
    detailTemplate = 'notification/fields/container'

    setup() {
        switch (this.model.get('type')) {
            case 'Note':
                this.processNote(this.model.get('noteData'));

                break;

            case 'MentionInPost':
                this.processMentionInPost(this.model.get('noteData'));

                break;

            default:
                this.process();
        }
    }

    process() {
        let type = this.model.get('type');

        if (!type) {
            return;
        }

        type = type.replace(/ /g, '');

        let viewName = this.getMetadata()
            .get('clientDefs.Notification.itemViews.' + type) ||
            'views/notification/items/' + Espo.Utils.camelCaseToHyphen(type);

        this.createView('notification', viewName, {
            model: this.model,
            fullSelector: this.options.containerSelector  + ' li[data-id="' + this.model.id + '"]',
        });
    }

    processNote(data) {
        if (!data) {
            return;
        }

        this.wait(true);

        this.getModelFactory().create('Note', model => {
            model.set(data);

            let viewName = this.getMetadata().get('clientDefs.Note.itemViews.' + data.type) ||
                'views/stream/notes/' + Espo.Utils.camelCaseToHyphen(data.type);

            this.createView('notification', viewName, {
                model: model,
                isUserStream: true,
                fullSelector: this.options.containerSelector  + ' li[data-id="' + this.model.id + '"]',
                onlyContent: true,
                isNotification: true,
            });

            this.wait(false);
        });
    }

    processMentionInPost(data) {
        if (!data) {
            return;
        }

        this.wait(true);

        this.getModelFactory().create('Note', model => {
            model.set(data);

            let viewName = 'views/stream/notes/mention-in-post';

            this.createView('notification', viewName, {
                model: model,
                userId: this.model.get('userId'),
                isUserStream: true,
                fullSelector: this.options.containerSelector + ' li[data-id="' + this.model.id + '"]',
                onlyContent: true,
                isNotification: true,
            });

            this.wait(false);
        });
    }
}

export default NotificationContainerFieldView;
PK]�c��w=w=views/notification/badge.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';

class NotificationBadgeView extends View {

    template = 'notification/badge'

    notificationsCheckInterval = 10
    groupedCheckInterval = 15

    /** @private */
    useWebSocket = false

    timeout = null
    groupedTimeout = null

    /**
     * @type {Object.<string, {
     *     portalDisabled?: boolean,
     *     grouped?: boolean,
     *     disabled?: boolean,
     *     interval?: Number,
     *     url?: string,
     *     useWebSocket?: boolean,
     *     view?: string,
     *     webSocketCategory?: string,
     * }>}
     */
    popupNotificationsData

    soundPath = 'client/sounds/pop_cork'

    setup() {
        this.addActionHandler('showNotifications', () => this.showNotifications());

        this.soundPath = this.getBasePath() + (this.getConfig().get('notificationSound') || this.soundPath);
        this.notificationSoundsDisabled = true;
        this.useWebSocket = !!this.getHelper().webSocketManager;

        let clearTimeouts = () => {
            if (this.timeout) {
                clearTimeout(this.timeout);
            }

            if (this.groupedTimeout) {
                clearTimeout(this.groupedTimeout);
            }

            for (let name in this.popupTimeouts) {
                clearTimeout(this.popupTimeouts[name]);
            }
        }

        this.once('remove', () => clearTimeouts());
        this.listenToOnce(this.getHelper().router, 'logout', () => clearTimeouts());

        this.notificationsCheckInterval = this.getConfig().get('notificationsCheckInterval') ||
            this.notificationsCheckInterval;

        this.groupedCheckInterval = this.getConfig().get('popupNotificationsCheckInterval') ||
            this.groupedCheckInterval;

        this.lastId = 0;
        this.shownNotificationIds = [];
        this.closedNotificationIds = [];
        this.popupTimeouts = {};

        delete localStorage['messageBlockPlayNotificationSound'];
        delete localStorage['messageClosePopupNotificationId'];
        delete localStorage['messageNotificationRead'];

        window.addEventListener('storage', e => {
            if (e.key === 'messageClosePopupNotificationId') {
                let id = localStorage.getItem('messageClosePopupNotificationId');

                if (id) {
                    let key = 'popup-' + id;

                    if (this.hasView(key)) {
                        this.markPopupRemoved(id);
                        this.clearView(key);
                    }
                }
            }

            if (e.key === 'messageNotificationRead') {
                if (
                    !this.isBroadcastingNotificationRead &&
                    localStorage.getItem('messageNotificationRead')
                ) {
                    this.checkUpdates();
                }
            }
        }, false);
    }

    afterRender() {
        this.$badge = this.$el.find('.notifications-button');
        this.$number = this.$el.find('.number-badge');

        this.runCheckUpdates(true);

        this.$popupContainer = $('#popup-notifications-container');

        if (!$(this.$popupContainer).length) {
            this.$popupContainer = $('<div>')
                .attr('id', 'popup-notifications-container')
                .addClass('hidden')
                .appendTo('body');
        }

        let popupNotificationsData = this.popupNotificationsData =
            this.getMetadata().get('app.popupNotifications') || {};

        for (let name in popupNotificationsData) {
            this.checkPopupNotifications(name);
        }

        if (this.hasGroupedPopupNotifications()) {
            this.checkGroupedPopupNotifications();
        }
    }

    playSound() {
        if (this.notificationSoundsDisabled) {
            return;
        }

        const audioElement =
            /** @type {HTMLAudioElement} */$('<audio>')
                .attr('autoplay', 'autoplay')
                .append(
                    $('<source>')
                        .attr('src', this.soundPath + '.mp3')
                        .attr('type', 'audio/mpeg')
                )
                .append(
                    $('<source>')
                        .attr('src', this.soundPath + '.ogg')
                        .attr('type', 'audio/ogg')
                )
                .append(
                    $('<embed>')
                        .attr('src', this.soundPath + '.mp3')
                        .attr('hidden', 'true')
                        .attr('autostart', 'true')
                        .attr('false', 'false')
                )
                .get(0);

        audioElement.volume = 0.3;
        audioElement.play();
    }

    showNotRead(count) {
        this.$badge.attr('title', this.translate('New notifications') + ': ' + count);

        this.$number.removeClass('hidden').html(count.toString());

        this.getHelper().pageTitle.setNotificationNumber(count);
    }

    hideNotRead() {
        this.$badge.attr('title', this.translate('Notifications'));
        this.$number.addClass('hidden').html('');

        this.getHelper().pageTitle.setNotificationNumber(0);
    }

    checkBypass() {
        let last = this.getRouter().getLast() || {};

        let pageAction = (last.options || {}).page || null;

        if (
            last.controller === 'Admin' &&
            last.action === 'page' &&
            ~['upgrade', 'extensions'].indexOf(pageAction)
        ) {
            return true;
        }

        return false;
    }

    checkUpdates(isFirstCheck) {
        if (this.checkBypass()) {
            return;
        }

        Espo.Ajax
            .getRequest('Notification/action/notReadCount')
            .then(count => {
                if (!isFirstCheck && count > this.unreadCount) {
                    let messageBlockPlayNotificationSound =
                        localStorage.getItem('messageBlockPlayNotificationSound');

                    if (!messageBlockPlayNotificationSound) {
                        this.playSound();

                        localStorage.setItem('messageBlockPlayNotificationSound', 'true');

                        setTimeout(() => {
                            delete localStorage['messageBlockPlayNotificationSound'];
                        }, this.notificationsCheckInterval * 1000);
                    }
                }

                this.unreadCount = count;

                if (count) {
                    this.showNotRead(count);

                    return;
                }

                this.hideNotRead();
            });
    }

    runCheckUpdates(isFirstCheck) {
        this.checkUpdates(isFirstCheck);

        if (this.useWebSocket) {
            this.getHelper().webSocketManager.subscribe('newNotification', () => {
                this.checkUpdates();
            });

            return;
        }

        this.timeout = setTimeout(
            () => this.runCheckUpdates(),
            this.notificationsCheckInterval * 1000
        );
    }

    /**
     * @private
     * @return {boolean}
     */
    hasGroupedPopupNotifications() {
        for (let name in this.popupNotificationsData) {
            let data = this.popupNotificationsData[name] || {};

            if (!data.grouped) {
                continue;
            }

            if (data.portalDisabled && this.getUser().isPortal()) {
                continue;
            }

            return true;
        }

        return false;
    }

    /**
     * @private
     */
    checkGroupedPopupNotifications() {
        if (!this.checkBypass()) {
            Espo.Ajax.getRequest('PopupNotification/action/grouped')
                .then(result => {
                    for (let type in result) {
                        let list = result[type];

                        list.forEach(item => this.showPopupNotification(type, item));
                    }
                });
        }

        if (this.useWebSocket) {
            return;
        }

        this.groupedTimeout = setTimeout(
            () => this.checkGroupedPopupNotifications(),
            this.groupedCheckInterval * 1000
        );
    }

    checkPopupNotifications(name, isNotFirstCheck) {
        let data = this.popupNotificationsData[name] || {};

        let url = data.url;
        let interval = data.interval;
        let disabled = data.disabled || false;

        if (disabled) {
            return;
        }

        if (data.portalDisabled && this.getUser().isPortal()) {
            return;
        }

        let useWebSocket = this.useWebSocket && data.useWebSocket;

        if (useWebSocket) {
            let category = 'popupNotifications.' + (data.webSocketCategory || name);

            this.getHelper().webSocketManager.subscribe(category, (c, response) => {
                if (!response.list) {
                    return;
                }

                response.list.forEach(item => {
                    this.showPopupNotification(name, item);
                });
            });
        }

        if (data.grouped) {
            return;
        }

        if (!url) {
            return;
        }

        if (!interval) {
            return;
        }

        (
            new Promise(resolve => {
                if (this.checkBypass()) {
                    resolve();

                    return;
                }

                Espo.Ajax
                    .getRequest(url)
                    .then(list =>
                        list.forEach(item =>
                            this.showPopupNotification(name, item, isNotFirstCheck)
                        )
                    )
                    .finally(() => resolve());
            })
        )
        .then(() => {
            if (useWebSocket) {
                return;
            }

            this.popupTimeouts[name] = setTimeout(
                () => this.checkPopupNotifications(name, true),
                interval * 1000
            );
        });
    }

    showPopupNotification(name, data, isNotFirstCheck) {
        let view = this.popupNotificationsData[name].view;

        if (!view) {
            return;
        }

        let id = data.id || null;

        if (id) {
            id = name + '_' + id;

            if (~this.shownNotificationIds.indexOf(id)) {
                let notificationView = this.getView('popup-' + id);

                if (notificationView) {
                    notificationView.trigger('update-data', data.data);
                }

                return;
            }

            if (~this.closedNotificationIds.indexOf(id)) {
                return;
            }
        }
        else {
            id = this.lastId++;
        }

        this.shownNotificationIds.push(id);

        this.createView('popup-' + id, view, {
            notificationData: data.data || {},
            notificationId: data.id,
            id: id,
            isFirstCheck: !isNotFirstCheck,
        }, view => {
            view.render();

            this.$popupContainer.removeClass('hidden');

            this.listenTo(view, 'remove', () => {
                this.markPopupRemoved(id);

                localStorage.setItem('messageClosePopupNotificationId', id);
            });
        });
    }

    markPopupRemoved(id) {
        let index = this.shownNotificationIds.indexOf(id);

        if (index > -1) {
            this.shownNotificationIds.splice(index, 1);
        }

        if (this.shownNotificationIds.length === 0) {
            this.$popupContainer.addClass('hidden');
        }

        this.closedNotificationIds.push(id);
    }

    broadcastNotificationsRead() {
        if (!this.useWebSocket) {
            return;
        }

        this.isBroadcastingNotificationRead = true;

        localStorage.setItem('messageNotificationRead', 'true');

        setTimeout(() => {
            this.isBroadcastingNotificationRead = false;
            delete localStorage['messageNotificationRead'];
        }, 500);
    }

    showNotifications() {
        this.closeNotifications();

        let $container = $('<div>').attr('id', 'notifications-panel');

        $container.appendTo(this.$el.find('.notifications-panel-container'));

        this.createView('panel', 'views/notification/panel', {
            fullSelector: '#notifications-panel',
        }, view => {
            view.render();

            this.$el.closest('.navbar-body').removeClass('in');

            this.listenTo(view, 'all-read', () => {
                this.hideNotRead();
                this.$el.find('.badge-circle-warning').remove();
                this.broadcastNotificationsRead();
            });

            this.listenTo(view, 'collection-fetched', () => {
                this.checkUpdates();
                this.broadcastNotificationsRead();
            });

            this.listenToOnce(view, 'close', () => {
                this.closeNotifications();
            });
        });

        let $document = $(document);

        $document.on('mouseup.notification', e => {
            if (!$container.is(e.target) && $container.has(e.target).length === 0) {
                if (!$(e.target).closest('div.modal-dialog').length) {
                    this.closeNotifications();
                }
            }
        });

        if (window.innerWidth < this.getThemeManager().getParam('screenWidthXs')) {
            this.listenToOnce(this.getRouter(), 'route', () => {
                this.closeNotifications();
            });
        }
    }

    closeNotifications() {
        let $container = $('#notifications-panel');

        $container.remove();

        let $document = $(document);

        if (this.hasView('panel')) {
            this.getView('panel').remove();
        }

        $document.off('mouseup.notification');
    }
}

export default NotificationBadgeView;
PK]CuY��!views/notification/record/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/notification/record/list */

import ListExpandedRecordView from 'views/record/list-expanded';

class NotificationListRecordView extends ListExpandedRecordView {

    /**
     * @name collection
     * @type module:collections/note
     * @memberOf NotificationListRecordView#
     */

    setup() {
        super.setup();

        this.listenTo(this.collection, 'sync', (c, r, options) => {
            if (!options.fetchNew) {
                return;
            }

            let lengthBeforeFetch = options.lengthBeforeFetch || 0;

            if (lengthBeforeFetch === 0) {
                this.reRender();

                return;
            }

            let $list = this.$el.find(this.listContainerEl);

            let rowCount = this.collection.length - lengthBeforeFetch;

            for (let i = rowCount - 1; i >= 0; i--) {
                let model = this.collection.at(i);

                $list.prepend(
                    $(this.getRowContainerHtml(model.id))
                );

                this.buildRow(i, model, view => {
                    view.render();
                });
            }
        });

        this.events['auxclick a[href][data-scope][data-id]'] = e => {
            let isCombination = e.button === 1 && (e.ctrlKey || e.metaKey);

            if (!isCombination) {
                return;
            }

            let $target = $(e.currentTarget);

            let id = $target.attr('data-id');
            let scope = $target.attr('data-scope');

            e.preventDefault();
            e.stopPropagation();

            this.actionQuickView({
                id: id,
                scope: scope,
            });
        };
    }

    showNewRecords() {
        this.collection.fetchNew();
    }
}

export default NotificationListRecordView;
PK]k�����views/notification/panel.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';

class NotificationPanelView extends View {

    template = 'notification/panel'

    setup() {
        this.addActionHandler('markAllNotificationsRead', () => this.actionMarkAllRead());
        this.addActionHandler('openNotifications', () => this.actionOpenNotifications());
        this.addActionHandler('closePanel', () => this.close());

        this.addHandler('keydown', '', /** KeyboardEvent */event => {
            if (event.code === 'Escape') {
                this.close();
            }
        })

        const promise =
            this.getCollectionFactory().create('Notification', collection => {
                this.collection = collection;
                this.collection.maxSize = this.getConfig().get('notificationsMaxSize') || 5;

                this.listenTo(this.collection, 'sync', () => {
                    this.trigger('collection-fetched');
                });
            });

        this.wait(promise);

        this.navbarPanelHeightSpace = this.getThemeManager().getParam('navbarPanelHeightSpace') || 100;
        this.navbarPanelBodyMaxHeight = this.getThemeManager().getParam('navbarPanelBodyMaxHeight') || 600;

        this.once('remove', () => {
            $(window).off('resize.notifications-height');

            if (this.overflowWasHidden) {
                $('body').css('overflow', 'unset');

                this.overflowWasHidden = false;
            }
        });
    }

    afterRender() {
        this.collection.fetch()
            .then(() => this.createRecordView())
            .then(view => view.render());

        let $window = $(window);

        $window.off('resize.notifications-height');
        $window.on('resize.notifications-height', this.processSizing.bind(this));

        this.processSizing();

        $('#navbar li.notifications-badge-container').addClass('open');

        this.$el.find('> .panel').focus();
    }

    onRemove() {
        $('#navbar li.notifications-badge-container').removeClass('open');
    }

    /**
     * @return {Promise<module:views/record/list-expanded>}
     */
    createRecordView() {
        let viewName = this.getMetadata()
                .get(['clientDefs', 'Notification', 'recordViews', 'list']) ||
            'views/notification/record/list';

        return this.createView('list', viewName, {
            selector: '.list-container',
            collection: this.collection,
            showCount: false,
            listLayout: {
                rows: [
                    [
                        {
                            name: 'data',
                            view: 'views/notification/fields/container',
                            options: {
                                containerSelector: this.getSelector(),
                            },
                        }
                    ]
                ],
                right: {
                    name: 'read',
                    view: 'views/notification/fields/read',
                    width: '10px',
                },
            }
        });
    }

    actionMarkAllRead() {
        Espo.Ajax.postRequest('Notification/action/markAllRead')
            .then(() => this.trigger('all-read'));
    }

    processSizing() {
        let $window = $(window);
        let windowHeight = $window.height();
        let windowWidth = $window.width();

        let diffHeight = this.$el.find('.panel-heading').outerHeight();

        let cssParams = {};

        if (windowWidth <= this.getThemeManager().getParam('screenWidthXs')) {
            cssParams.height = (windowHeight - diffHeight) + 'px';
            cssParams.overflow = 'auto';

            $('body').css('overflow', 'hidden');
            this.overflowWasHidden = true;

            this.$el.find('.panel-body').css(cssParams);

            return;
        }

        cssParams.height = 'unset';
        cssParams.overflow = 'none';

        if (this.overflowWasHidden) {
            $('body').css('overflow', 'unset');

            this.overflowWasHidden = false;
        }

        if (windowHeight - this.navbarPanelBodyMaxHeight < this.navbarPanelHeightSpace) {
            let maxHeight = windowHeight - this.navbarPanelHeightSpace;

            cssParams.maxHeight = maxHeight + 'px';
        }

        this.$el.find('.panel-body').css(cssParams);
    }

    close() {
        this.trigger('close');
    }

    actionOpenNotifications() {
        this.getRouter().navigate('#Notification', {trigger: true});

        this.close();
    }
}

export default NotificationPanelView;
PK]d���views/notification/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';

class NotificationListView extends View {

    template = 'notification/list'

    setup() {
        this.addActionHandler('refresh', () => this.getRecordView().showNewRecords());
        this.addActionHandler('markAllNotificationsRead', () => this.actionMarkAllRead());

        const promise =
            this.getCollectionFactory().create('Notification')
                .then(collection => {
                    this.collection = collection;
                    this.collection.maxSize = this.getConfig().get('recordsPerPage') || 20;
                })

        this.wait(promise);
    }

    afterRender() {
        let viewName = this.getMetadata()
            .get(['clientDefs', 'Notification', 'recordViews', 'list']) ||
            'views/notification/record/list';

        let options = {
            selector: '.list-container',
            collection: this.collection,
            showCount: false,
            listLayout: {
                rows: [
                    [
                        {
                            name: 'data',
                            view: 'views/notification/fields/container',
                            options: {
                                containerSelector: this.getSelector(),
                            },
                        },
                    ],
                ],
                right: {
                    name: 'read',
                    view: 'views/notification/fields/read-with-menu',
                    width: '10px',
                },
            },
        };

        this.collection
            .fetch()
            .then(() => this.createView('list', viewName, options))
            .then(view => view.render())
            .then(view => {
                view.$el.find('> .list > .list-group');
            });
    }

    actionMarkAllRead() {
        Espo.Ajax.postRequest('Notification/action/markAllRead')
            .then(() => {
                this.trigger('all-read');

                this.$el.find('.badge-circle-warning').remove();
            });
    }

    /**
     * @return {module:views/notification/record/list}
     */
    getRecordView() {
        return this.getView('list');
    }
}

export default NotificationListView;
PK]�#JJ"views/notification/items/system.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import BaseNotificationItemView from 'views/notification/items/base';

class SystemNotificationItemView extends BaseNotificationItemView {

    template = 'notification/items/system'

    data() {
        return {
            ...super.data(),
            message: this.model.get('message'),
        };
    }

    setup() {
        let data = this.model.get('data') || {};

        this.userId = data.userId;
    }
}

export default SystemNotificationItemView;
PK]����v	v	"views/notification/items/assign.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import BaseNotificationItemView from 'views/notification/items/base';

class AssignNotificationItemView extends BaseNotificationItemView {

    messageName = 'assign'

    template = 'notification/items/assign'

    setup() {
        let data = this.model.get('data') || {};

        this.userId = data.userId;

        this.messageData['entityType'] = this.translateEntityType(data.entityType);

        this.messageData['entity'] =
            $('<a>')
                .attr('href', '#' + data.entityType + '/view/' + data.entityId)
                .attr('data-id', data.entityId)
                .attr('data-scope', data.entityType)
                .text(data.entityName);

        this.messageData['user'] =
            $('<a>')
                .attr('href', '#User/view/' + data.userId)
                .attr('data-id', data.userId)
                .attr('data-scope', 'User')
                .text(data.userName);

        this.createMessage();
    }
}

export default AssignNotificationItemView;
PK]�11 views/notification/items/base.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/notification/items/base */

import View from 'view';

class BaseNotificationItemView extends View {

    /** @type {string} */
    messageName
    /** @type {string} */
    messageTemplate
    messageData = null
    isSystemAvatar = false

    data() {
        return {
            avatar: this.getAvatarHtml(),
        };
    }

    init() {
        this.createField('createdAt', null, null, 'views/fields/datetime-short');

        this.messageData = {};
    }

    createField(name, type, params, view) {
        type = type || this.model.getFieldType(name) || 'base';

        this.createView(name, view || this.getFieldManager().getViewName(type), {
            model: this.model,
            defs: {
                name: name,
                params: params || {}
            },
            selector: '.cell-' + name,
            mode: 'list',
        });
    }

    createMessage() {
        let parentType = this.model.get('relatedParentType') || null;

        if (!this.messageTemplate && this.messageName) {
            this.messageTemplate = this.translate(this.messageName, 'notificationMessages', parentType) || '';
        }

        if (
            this.messageTemplate.indexOf('{entityType}') === 0 &&
            typeof this.messageData.entityType === 'string'
        ) {
            this.messageData.entityTypeUcFirst = Espo.Utils.upperCaseFirst(this.messageData.entityType);

            this.messageTemplate = this.messageTemplate.replace('{entityType}', '{entityTypeUcFirst}');
        }

        this.createView('message', 'views/stream/message', {
            messageTemplate: this.messageTemplate,
            selector: '.message',
            model: this.model,
            messageData: this.messageData,
        });
    }

    getAvatarHtml() {
        let id = this.userId;

        if (this.isSystemAvatar || !id) {
            id = this.getHelper().getAppParam('systemUserId');
        }

        return this.getHelper().getAvatarHtml(id, 'small', 20);
    }

    /**
     * @param {string} entityType
     * @param {boolean} [isPlural]
     * @return {string}
     */
    translateEntityType(entityType, isPlural) {
        let string = isPlural ?
            (this.translate(entityType, 'scopeNamesPlural') || '') :
            (this.translate(entityType, 'scopeNames') || '');

        string = string.toLowerCase();

        let language = this.getPreferences().get('language') || this.getConfig().get('language');

        if (~['de_DE', 'nl_NL'].indexOf(language)) {
            string = Espo.Utils.upperCaseFirst(string);
        }

        return string;
    }
}

export default BaseNotificationItemView;
PK]���	�	*views/notification/items/entity-removed.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import BaseNotificationItemView from 'views/notification/items/base';

class EmailRemovedNotificationItemView extends BaseNotificationItemView {

    messageName = 'entityRemoved'

    template = 'notification/items/entity-removed'

    setup() {
        let data = /** @type Object.<string, *> */this.model.get('data') || {};

        this.userId = data.userId;

        this.messageData['entityType'] = this.translateEntityType(data.entityType);

        this.messageData['user'] =
            $('<a>')
                .attr('href', '#User/view/' + data.userId)
                .attr('data-id', data.userId)
                .attr('data-scope', 'User')
                .text(data.userName);

        this.messageData['entity'] =
            $('<a>')
                .attr('href', '#' + data.entityType + '/view/' + data.entityId)
                .attr('data-id', data.entityId)
                .attr('data-scope', data.entityType)
                .text(data.entityName);

        this.createMessage();
    }
}

export default EmailRemovedNotificationItemView;
PK]�����
�
*views/notification/items/email-received.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import BaseNotificationItemView from 'views/notification/items/base';

class EmailReceivedNotificationItemView extends BaseNotificationItemView {

    messageName = 'emailReceived'

    template = 'notification/items/email-received'

    data() {
        return {
            ...super.data(),
            emailId: this.emailId,
            emailName: this.emailName,
        };
    }

    setup() {
        let data = /** @type Object.<string, *> */this.model.get('data') || {};

        this.userId = data.userId;

        this.messageData['entityType'] = this.translateEntityType(data.entityType);

        if (data.personEntityId) {
            this.messageData['from'] =
                $('<a>')
                    .attr('href', '#' + data.personEntityType + '/view/' + data.personEntityId)
                    .attr('data-id', data.personEntityId)
                    .attr('data-scope', data.personEntityType)
                    .text(data.personEntityName);
        }
        else {
            let text = data.fromString || this.translate('empty address');

            this.messageData['from'] = $('<span>').text(text);
        }

        this.emailId = data.emailId;
        this.emailName = data.emailName;

        this.createMessage();
    }
}

export default EmailReceivedNotificationItemView;
PK]q�
//#views/notification/items/message.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import BaseNotificationItemView from 'views/notification/items/base';
import {marked} from 'marked';
import DOMPurify from 'dompurify';

class MessageNotificationItemView extends BaseNotificationItemView {

    template = 'notification/items/message'

    data() {
        return {
            ...super.data(),
            style: this.style,
        };
    }

    setup() {
        let data = /** @type Object.<string, *> */this.model.get('data') || {};

        let messageRaw = this.model.get('message') || data.message || '';
        let message = marked.parse(messageRaw);

        this.messageTemplate = DOMPurify.sanitize(message, {}).toString();

        this.userId = data.userId;
        this.style = data.style || 'text-muted';

        this.messageData['entityType'] = this.translateEntityType(data.entityType);

        this.messageData['user'] =
            $('<a>')
                .attr('href', '#User/view/' + data.userId)
                .attr('data-id', data.userId)
                .attr('data-scope', 'User')
                .text(data.userName);

        this.messageData['entity'] =
            $('<a>')
                .attr('href', '#' + data.entityType + '/view/' + data.entityId)
                .attr('data-id', data.entityId)
                .attr('data-scope', data.entityType)
                .text(data.entityName);

        this.createMessage();
    }
}

export default MessageNotificationItemView;
PK]��m��	�	views/clear-cache.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';

class ClearCacheView extends View {

    template = 'clear-cache'

    el = '> body'

    events = {
        /** @this ClearCacheView */
        'click .action[data-action="clearLocalCache"]': function () {
            this.clearLocalCache();
        },
        /** @this ClearCacheView */
        'click .action[data-action="returnToApplication"]': function () {
            this.returnToApplication();
        }
    }

    data() {
        return {
            cacheIsEnabled: !!this.options.cache
        };
    }

    clearLocalCache() {
        this.options.cache.clear();

        this.$el.find('.action[data-action="clearLocalCache"]').remove();
        this.$el.find('.message-container').removeClass('hidden');
        this.$el.find('.message-container span').html(this.translate('Cache has been cleared'));
        this.$el.find('.action[data-action="returnToApplication"]').removeClass('hidden');
    }

    returnToApplication() {
        this.getRouter().navigate('', {trigger: true});
    }
}

export default ClearCacheView;
PK]s-3��#views/email-template/fields/body.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email-template/fields/body', ['views/fields/wysiwyg'], function (Dep) {

    return Dep.extend({});
});
PK]��Rݫ3�3+views/email-template/fields/insert-field.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email-template/fields/insert-field', ['views/fields/base', 'ui/select'],
function (Dep, /** module:ui/select */Select) {

    return Dep.extend({

        inlineEditDisabled: true,

        detailTemplate: 'email-template/fields/insert-field/detail',
        editTemplate: 'email-template/fields/insert-field/edit',

        data: function () {
            return {};
        },

        events: {
            'click [data-action="insert"]': function () {
                var entityType = this.$entityType.val();
                var field = this.$field.val();

                if (!field) {
                    return;
                }

                this.insert(entityType, field);
            },
        },

        setup: function () {
            if (this.mode !== this.MODE_LIST) {
                var entityList = [];

                var defs = this.getMetadata().get('scopes');

                entityList = Object.keys(defs).filter(scope => {
                    if (scope === 'Email') {
                        return;
                    }

                    if (!this.getAcl().checkScope(scope)) {
                        return;
                    }

                    return (defs[scope].entity && (defs[scope].object));
                });

                this.translatedOptions = {};

                var entityPlaceholders = {};

                entityList.forEach(scope => {
                    this.translatedOptions[scope] = {};

                    entityPlaceholders[scope] = this.getScopeAttributeList(scope);

                    entityPlaceholders[scope].forEach(item => {
                        this.translatedOptions[scope][item] = this.translatePlaceholder(scope, item);
                    });

                    var links = this.getMetadata().get('entityDefs.' + scope + '.links') || {};

                    var linkList = Object.keys(links).sort((v1, v2) => {
                        return this.translate(v1, 'links', scope).localeCompare(this.translate(v2, 'links', scope));
                    });

                    linkList.forEach((link) => {
                        var type = links[link].type

                        if (type !== 'belongsTo') {
                            return;
                        }

                        var foreignScope = links[link].entity;

                        if (!foreignScope) {
                            return;
                        }

                        if (links[link].disabled || links[link].utility) {
                            return;
                        }

                        if (
                            this.getMetadata().get(['entityAcl', scope, 'links', link, 'onlyAdmin']) ||
                            this.getMetadata().get(['entityAcl', scope, 'links', link, 'forbidden']) ||
                            this.getMetadata().get(['entityAcl', scope, 'links', link, 'internal'])
                        ) {
                            return;
                        }

                        var attributeList = this.getScopeAttributeList(foreignScope);

                        attributeList.forEach((item) => {
                            entityPlaceholders[scope].push(link + '.' + item);

                            this.translatedOptions[scope][link + '.' + item] =
                                this.translatePlaceholder(scope, link + '.' + item);
                        });
                    });
                });

                entityPlaceholders['Person'] =
                    ['name', 'firstName', 'lastName', 'salutationName', 'emailAddress', 'assignedUserName'];

                this.translatedOptions['Person'] = {};

                this.entityList = entityList;
                this.entityFields = entityPlaceholders;
            }
        },

        getScopeAttributeList: function (scope) {
            var fieldList = this.getFieldManager().getEntityTypeFieldList(scope);

            var list = [];

            fieldList = fieldList.sort((v1, v2) => {
                return this.translate(v1, 'fields', scope).localeCompare(this.translate(v2, 'fields', scope));
            });

            fieldList.forEach(field => {
                var fieldType = this.getMetadata().get(['entityDefs', scope, 'fields', field, 'type']);

                let aclDefs = this.getMetadata().get(['entityAcl', scope, 'fields', field]) || {};
                let fieldDefs = this.getMetadata().get(['entityDefs', scope, 'fields', field]) || {};

                if (
                    aclDefs.onlyAdmin ||
                    aclDefs.forbidden ||
                    aclDefs.internal ||
                    fieldDefs.disabled ||
                    fieldDefs.utility ||
                    fieldDefs.directAccessDisabled ||
                    fieldDefs.templatePlaceholderDisabled
                ) {
                    return false;
                }

                if (fieldType === 'map') return;
                if (fieldType === 'linkMultiple') return;
                if (fieldType === 'attachmentMultiple') return;

                if (
                    this.getMetadata().get(['entityAcl', scope, 'fields', field, 'onlyAdmin']) ||
                    this.getMetadata().get(['entityAcl', scope, 'fields', field, 'forbidden']) ||
                    this.getMetadata().get(['entityAcl', scope, 'fields', field, 'internal'])
                ) {
                    return;
                }

                var fieldAttributeList = this.getFieldManager().getAttributeList(fieldType, field);

                fieldAttributeList.forEach((attribute) => {
                    if (~list.indexOf(attribute)) {
                        return;
                    }

                    list.push(attribute);
                });
            });

            var forbiddenList = this.getAcl().getScopeForbiddenAttributeList(scope);

            list = list.filter((item) => {
                if (~forbiddenList.indexOf(item)) {
                    return;
                }

                return true;
            });

            list.push('id');

            if (this.getMetadata().get('entityDefs.' + scope + '.fields.name.type') === 'personName') {
                list.unshift('name');
            }

            return list;
        },

        translatePlaceholder: function (entityType, item) {
            var field = item;
            var scope = entityType;
            var isForeign = false;

            if (~item.indexOf('.')) {
                isForeign = true;
                field = item.split('.')[1];
                var link = item.split('.')[0];

                scope = this.getMetadata().get('entityDefs.' + entityType + '.links.' + link + '.entity');
            }

            var label = item;

            label = this.translate(field, 'fields', scope);

            if (field.indexOf('Id') === field.length - 2) {
                var baseField = field.substr(0, field.length - 2);

                if (this.getMetadata().get(['entityDefs', scope, 'fields', baseField])) {
                    label = this.translate(baseField, 'fields', scope) + ' (' + this.translate('id', 'fields') + ')';
                }
            }
            else if (field.indexOf('Name') === field.length - 4) {
                var baseField = field.substr(0, field.length - 4);

                if (this.getMetadata().get(['entityDefs', scope, 'fields', baseField])) {
                    label = this.translate(baseField, 'fields', scope) + ' (' + this.translate('name', 'fields') + ')';
                }
            }
            else if (field.indexOf('Type') === field.length - 4) {
                var baseField = field.substr(0, field.length - 4);

                if (this.getMetadata().get(['entityDefs', scope, 'fields', baseField])) {
                    label = this.translate(baseField, 'fields', scope) + ' (' + this.translate('type', 'fields') + ')';
                }
            }

            if (field.indexOf('Ids') === field.length - 3) {
                var baseField = field.substr(0, field.length - 3);

                if (this.getMetadata().get(['entityDefs', scope, 'fields', baseField])) {
                    label = this.translate(baseField, 'fields', scope) + ' (' + this.translate('ids', 'fields') + ')';
                }
            }
            else if (field.indexOf('Names') === field.length - 5) {
                var baseField = field.substr(0, field.length - 5);

                if (this.getMetadata().get(['entityDefs', scope, 'fields', baseField])) {
                    label = this.translate(baseField, 'fields', scope) + ' (' + this.translate('names', 'fields') + ')';
                }
            }
            else if (field.indexOf('Types') === field.length - 5) {
                var baseField = field.substr(0, field.length - 5);

                if (this.getMetadata().get(['entityDefs', scope, 'fields', baseField])) {
                    label = this.translate(baseField, 'fields', scope) + ' (' + this.translate('types', 'fields') + ')';
                }
            }

            if (isForeign) {
                label = this.translate(link, 'links', entityType) + '.' + label;
            }

            return label;
        },

        afterRender: function () {
            Dep.prototype.afterRender.call(this);

            if (this.mode === this.MODE_EDIT) {
                var entityTranslation = {};

                this.entityList.forEach((scope) => {
                    entityTranslation[scope] = this.translate(scope, 'scopeNames');
                });

                this.entityList.sort((a, b) => {
                    return a.localeCompare(b);
                });

                var $entityType = this.$entityType = this.$el.find('[data-name="entityType"]');

                this.$field = this.$el.find('[data-name="field"]');

                $entityType.on('change', () => {
                    this.changeEntityType();
                });

                $entityType.append(
                    $('<option>')
                        .val('Person')
                        .text(this.translate('Person'))
                );

                this.entityList.forEach(scope => {
                    $entityType.append(
                        $('<option>')
                            .val(scope)
                            .text(entityTranslation[scope])
                    );
                });

                Select.init(this.$field);

                this.changeEntityType();

                Select.init(this.$entityType);
            }
        },

        changeEntityType: function () {
            var entityType = this.$entityType.val();
            var fieldList = this.entityFields[entityType];

            Select.setValue(this.$field, '');

            Select.setOptions(this.$field, fieldList.map(field => {
                return {
                    value: field,
                    label: this.translateItem(entityType, field),
                };
            }));
        },

        translateItem: function (entityType, item) {
            if (this.translatedOptions[entityType][item]) {
                return this.translatedOptions[entityType][item];
            }

            return this.translate(item, 'fields');
        },

        insert: function (entityType, field) {
            this.model.trigger('insert-field', {
                entityType: entityType,
                field: field,
            });
        }
    });
});
PK]>�,��)views/email-template/record/edit-quick.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email-template/record/edit-quick',
['views/record/edit', 'views/email-template/record/detail'], function (Dep, Detail) {

    return Dep.extend({

    	isWide: true,
        sideView: false,

        setup: function () {
            Dep.prototype.setup.call(this);
            Detail.prototype.listenToInsertField.call(this);
        },
    });
});
PK]��

1views/email-template/record/panels/information.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email-template/record/panels/information', ['views/record/panels/side'], function (Dep) {

    return Dep.extend({

        templateContent: '{{{infoText}}}',

        data: function () {
            let placeholderList = this.getMetadata().get(['clientDefs', 'EmailTemplate', 'placeholderList']) || [];

            if (!placeholderList.length) {
                return {
                    infoText: ''
                };
            }

            let $header = $('<h4>').text(this.translate('Available placeholders', 'labels', 'EmailTemplate') + ':');

            let $liList = placeholderList.map(item => {
                return $('<li>').append(
                    $('<code>').text('{' + item + '}'),
                    ' &#8211; ',
                    $('<span>').text(this.translate(item, 'placeholderTexts', 'EmailTemplate'))
                )
            });

            let $ul = $('<ul>').append($liList);

            let $text = $('<span>')
                .addClass('complex-text')
                .append($header, $ul)

            return {
                infoText: $text[0].outerHTML,
            };
        },
    });
});
PK]��_=��#views/email-template/record/edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email-template/record/edit', ['views/record/edit', 'views/email-template/record/detail'], function (Dep, Detail) {

    return Dep.extend({

        saveAndContinueEditingAction: true,

        setup: function () {
            Dep.prototype.setup.call(this);
            Detail.prototype.listenToInsertField.call(this);
        },

    });
});
PK]�n0""%views/email-template/record/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email-template/record/detail', ['views/record/detail'], function (Dep) {

    return Dep.extend({

        duplicateAction: true,

        saveAndContinueEditingAction: true,

        setup: function () {
            Dep.prototype.setup.call(this);
            this.listenToInsertField();


            this.hideField('insertField');

            this.on('before:set-edit-mode', function () {
                this.showField('insertField');
            }, this);

            this.on('before:set-detail-mode', function () {
                this.hideField('insertField');
            }, this);
        },

        listenToInsertField: function () {
            this.listenTo(this.model, 'insert-field', function (o) {
                var tag = '{' + o.entityType + '.' + o.field + '}';

                var bodyView = this.getFieldView('body');
                if (!bodyView) return;

                if (this.model.get('isHtml')) {
                    var $anchor = $(window.getSelection().anchorNode);
                    if (!$anchor.closest('.note-editing-area').length) return;
                    bodyView.$summernote.summernote('insertText', tag);
                } else {
                    var $body = bodyView.$element;
                    var text = $body.val();
                    text += tag;
                    $body.val(text);
                }
            }, this);
        },
    });
});
PK]<3�views/email-template/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email-template/list', ['views/list-with-categories'], function (Dep) {

    return Dep.extend({

        quickCreate: false,
    });
});
PK].����
views/edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module module:views/edit */

import MainView from 'views/main';

/**
 * An edit view.
 */
class EditView extends MainView {

    /** @inheritDoc */
    template = 'edit'

    /** @inheritDoc */
    name = 'Edit'

    /** @inheritDoc */
    optionsToPass = [
        'returnUrl',
        'returnDispatchParams',
        'attributes',
        'rootUrl',
        'duplicateSourceId',
        'returnAfterCreate',
    ]

    /**
     * A header view name.
     *
     * @type {string}
     */
    headerView = 'views/header'

    /**
     * A record view name.
     *
     * @type {string}
     */
    recordView = 'views/record/edit'

    /**
     * A root breadcrumb item not to be a link.
     *
     * @type {boolean}
     */
    rootLinkDisabled = false

    /** @inheritDoc */
    setup() {
        this.headerView = this.options.headerView || this.headerView;
        this.recordView = this.options.recordView || this.recordView;

        this.setupHeader();
        this.setupRecord();
    }

    /** @inheritDoc */
    setupFinal() {
        super.setupFinal();

        this.wait(
            this.getHelper().processSetupHandlers(this, 'edit')
        );
    }

    /**
     * Set up a header.
     */
    setupHeader() {
        this.createView('header', this.headerView, {
            model: this.model,
            fullSelector: '#main > .header',
            scope: this.scope,
        });
    }

    /**
     * Set up a record.
     */
    setupRecord() {
        let o = {
            model: this.model,
            fullSelector: '#main > .record',
            scope: this.scope,
            shortcutKeysEnabled: true,
        };

        this.optionsToPass.forEach(option => {
            o[option] = this.options[option];
        });

        let params = this.options.params || {};

        if (params.rootUrl) {
            o.rootUrl = params.rootUrl;
        }

        if (params.focusForCreate) {
            o.focusForCreate = true;
        }

        return this.createView('record', this.getRecordViewName(), o);
    }

    /**
     * Get a record view name.
     *
     * @returns {string}
     */
    getRecordViewName() {
        return this.getMetadata().get('clientDefs.' + this.scope + '.recordViews.edit') || this.recordView;
    }

    /** @inheritDoc */
    getHeader() {
        let headerIconHtml = this.getHeaderIconHtml();
        let rootUrl = this.options.rootUrl || this.options.params.rootUrl || '#' + this.scope;
        let scopeLabel = this.getLanguage().translate(this.scope, 'scopeNamesPlural');

        let $root = $('<span>').text(scopeLabel);

        if (!this.options.noHeaderLinks && !this.rootLinkDisabled) {
            $root =
                $('<span>')
                    .append(
                        $('<a>')
                            .attr('href', rootUrl)
                            .addClass('action')
                            .attr('data-action', 'navigateToRoot')
                            .text(scopeLabel)
                    );
        }

        if (headerIconHtml) {
            $root.prepend(headerIconHtml);
        }

        if (this.model.isNew()) {
            let $create = $('<span>').text(this.getLanguage().translate('create'));

            return this.buildHeaderHtml([$root, $create]);
        }

        let name = this.model.get('name') || this.model.id;

        let $name = $('<span>').text(name);

        if (!this.options.noHeaderLinks) {
            let url = '#' + this.scope + '/view/' + this.model.id;

            $name =
                $('<a>')
                    .attr('href', url)
                    .addClass('action')
                    .append($name);
        }

        return this.buildHeaderHtml([$root, $name]);
    }

    /** @inheritDoc */
    updatePageTitle() {
        if (this.model.isNew()) {
            let title = this.getLanguage().translate('Create') + ' ' +
                this.getLanguage().translate(this.scope, 'scopeNames');

            this.setPageTitle(title);

            return;
        }

        let name = this.model.get('name');

        let title = name ? name : this.getLanguage().translate(this.scope, 'scopeNames');

        this.setPageTitle(title);
    }
}

export default EditView;
PK]��t��
views/base.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/base */

import View from 'view';

class BaseView extends View {

    /**
     * @typedef {Object} module:views/base~options
     * @property {string} [template] A template.
     */

    /**
     * @param {module:views/base~options & Object.<string, *>} [options] Options.
     */
    constructor(options) {
        super(options);
    }
}

export default BaseView
PK]60�H
H
views/collapsed-modal.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';

class CollapsedModalView extends View {

    templateContent = `
        <div class="title-container">
            <a role="button" data-action="expand" class="title">{{title}}</a>
        </div>
        <div class="close-container">
            <a role="button" data-action="close"><span class="fas fa-times"></span></a>
        </div>
    `

    events = {
        /** @this CollapsedModalView */
        'click [data-action="expand"]': function () {
            this.expand();
        },
        /** @this CollapsedModalView */
        'click [data-action="close"]': function () {
            this.close();
        },
    }

    data() {
        let title = this.title;

        if (this.duplicateNumber) {
            title = this.title + ' ' + this.duplicateNumber;
        }

        return {
            title: title,
        };
    }

    setup() {
        this.title = this.options.title || 'no-title';
        this.duplicateNumber = this.options.duplicateNumber || null;
    }

    expand() {
        this.trigger('expand');
    }

    close() {
        this.trigger('close');
    }
}

// noinspection JSUnusedGlobalSymbols
export default CollapsedModalView;
PK]��Yviews/template/fields/body.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/template/fields/body', ['views/fields/wysiwyg'], function (Dep) {

    return Dep.extend({

        htmlPurificationForEditDisabled: true,

    });
});
PK]?�d�B�B"views/template/fields/variables.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/template/fields/variables', ['views/fields/base', 'ui/select'],
function (Dep, /** module:ui/select */Select) {

    return Dep.extend({

        inlineEditDisabled: true,

        detailTemplate: 'template/fields/variables/detail',
        editTemplate: 'template/fields/variables/edit',

        data: function () {
            return {
                attributeList: this.attributeList,
                entityType: this.model.get('entityType'),
                translatedOptions: this.translatedOptions
            };
        },

        events: {
            'change [data-name="variables"]': function () {
                var attribute = this.$el.find('[data-name="variables"]').val();

                var $copy = this.$el.find('[data-name="copy"]');

                if (attribute !== '') {
                    if (this.textVariables[attribute]) {
                        $copy.val('{{{' + attribute + '}}}');
                    } else {
                        $copy.val('{{' + attribute + '}}');
                    }
                } else {
                    $copy.val('');
                }
            }
        },

        setup: function () {
            this.setupAttributeList();
            this.setupTranslatedOptions();

            this.listenTo(this.model, 'change:entityType', () => {
                this.setupAttributeList();
                this.setupTranslatedOptions();
                this.reRender();
            });
        },

        setupAttributeList: function () {
            this.translatedOptions = {};

            var entityType = this.model.get('entityType');

            var fieldList = this.getFieldManager().getEntityTypeFieldList(entityType);

            var ignoreFieldList = [];

            fieldList.forEach(field => {
                let aclDefs = this.getMetadata().get(['entityAcl', entityType, 'fields', field]) || {};
                let fieldDefs = this.getMetadata().get(['entityDefs', entityType, 'fields', field]) || {};

                if (
                    aclDefs.onlyAdmin ||
                    aclDefs.forbidden ||
                    aclDefs.internal ||
                    fieldDefs.disabled ||
                    fieldDefs.utility ||
                    fieldDefs.directAccessDisabled ||
                    fieldDefs.templatePlaceholderDisabled
                ) {
                    ignoreFieldList.push(field);
                }
            });

            var attributeList = this.getFieldManager().getEntityTypeAttributeList(entityType) || [];

            var forbiddenList = Espo.Utils.clone(this.getAcl().getScopeForbiddenAttributeList(entityType));

            ignoreFieldList.forEach((field) => {
                this.getFieldManager().getEntityTypeFieldAttributeList(entityType, field).forEach(function (attribute) {
                    forbiddenList.push(attribute);
                });
            });

            attributeList = attributeList.filter((item) => {
                if (~forbiddenList.indexOf(item)) return;

                var fieldType = this.getMetadata().get(['entityDefs', entityType, 'fields', item, 'type']);

                if (fieldType === 'map') {
                    return;
                }

                return true;
            });


            attributeList.push('id');

            if (this.getMetadata().get('entityDefs.' + entityType + '.fields.name.type') === 'personName') {
                if (!~attributeList.indexOf('name')) {
                    attributeList.unshift('name');
                }
            }

            this.addAdditionalPlaceholders(entityType, attributeList);

            attributeList = attributeList.sort((v1, v2) => {
                return this.translate(v1, 'fields', entityType).localeCompare(this.translate(v2, 'fields', entityType));
            });

            this.attributeList = attributeList;

            this.textVariables = {};

            this.attributeList.forEach((item) => {
                if (
                    ~['text', 'wysiwyg']
                        .indexOf(this.getMetadata().get(['entityDefs', entityType, 'fields', item, 'type']))
                ) {
                    this.textVariables[item] = true;
                }
            });

            if (!~this.attributeList.indexOf('now')) {
                this.attributeList.unshift('now');
            }

            if (!~this.attributeList.indexOf('today')) {
                this.attributeList.unshift('today');
            }

            attributeList.unshift('pagebreak');

            this.attributeList.unshift('');

            var links = this.getMetadata().get('entityDefs.' + entityType + '.links') || {};

            var linkList = Object.keys(links).sort((v1, v2) => {
                return this.translate(v1, 'links', entityType).localeCompare(this.translate(v2, 'links', entityType));
            });

            linkList.forEach((link) => {
                var type = links[link].type;

                if (type !== 'belongsTo') {
                    return;
                }

                var scope = links[link].entity;
                if (!scope) return;

                if (links[link].disabled || links[link].utility) {
                    return;
                }

                if (
                    this.getMetadata().get(['entityAcl', entityType, 'links', link, 'onlyAdmin'])
                    ||
                    this.getMetadata().get(['entityAcl', entityType, 'links', link, 'forbidden'])
                    ||
                    this.getMetadata().get(['entityAcl', entityType, 'links', link, 'internal'])
                ) {
                    return;
                }

                var fieldList = this.getFieldManager().getEntityTypeFieldList(scope);

                var ignoreFieldList = [];

                fieldList.forEach(field => {
                    let aclDefs = this.getMetadata().get(['entityAcl', entityType, 'fields', field]) || {};
                    let fieldDefs = this.getMetadata().get(['entityDefs', entityType, 'fields', field]) || {};

                    if (
                        aclDefs.onlyAdmin ||
                        aclDefs.forbidden ||
                        aclDefs.internal ||
                        fieldDefs.disabled ||
                        fieldDefs.utility ||
                        fieldDefs.directAccessDisabled ||
                        fieldDefs.templatePlaceholderDisabled
                    ) {
                        ignoreFieldList.push(field);
                    }
                });

                var attributeList = this.getFieldManager().getEntityTypeAttributeList(scope) || [];

                var forbiddenList = Espo.Utils.clone(this.getAcl().getScopeForbiddenAttributeList(scope));

                ignoreFieldList.forEach((field) => {
                    this.getFieldManager().getEntityTypeFieldAttributeList(scope, field).forEach((attribute) => {
                        forbiddenList.push(attribute);
                    });
                });

                attributeList = attributeList.filter((item) => {
                    if (~forbiddenList.indexOf(item)) {
                        return;
                    }

                    var fieldType = this.getMetadata().get(['entityDefs', scope, 'fields', item, 'type']);

                    if (fieldType === 'map') {
                        return;
                    }

                    return true;
                });

                attributeList.push('id');

                if (this.getMetadata().get('entityDefs.' + scope + '.fields.name.type') === 'personName') {
                    attributeList.unshift('name');
                }

                var originalAttributeList = Espo.Utils.clone(attributeList);

                this.addAdditionalPlaceholders(scope, attributeList, link, entityType);

                attributeList.sort((v1, v2) => {
                    return this.translate(v1, 'fields', scope).localeCompare(this.translate(v2, 'fields', scope));
                });

                attributeList.forEach((item) => {
                    if (~originalAttributeList.indexOf(item)) {
                        this.attributeList.push(link + '.' + item);
                    } else {
                        this.attributeList.push(item);
                    }
                });

                attributeList.forEach((item) => {
                    var variable = link + '.' + item;

                    if (
                        ~['text', 'wysiwyg']
                            .indexOf(this.getMetadata().get(['entityDefs', scope, 'fields', item, 'type']))
                    ) {
                        this.textVariables[variable] = true;
                    }
                });
            });

            return this.attributeList;
        },

        addAdditionalPlaceholders: function (entityType, attributeList, link, superEntityType) {
            function removeItem(attributeList, item) {
                for (var i = 0; i < attributeList.length; i++) {
                    if (attributeList[i] === item) {
                        attributeList.splice(i, 1);
                    }
                }
            }

            var fieldDefs = this.getMetadata().get(['entityDefs', entityType, 'fields']) || {};

            for (var field in fieldDefs) {
                var fieldType = fieldDefs[field].type;

                var item = field;
                if (link) item = link + '.' + item;

                var cAttributeList = Espo.Utils.clone(attributeList);

                if (fieldType === 'image') {
                    removeItem(attributeList, field + 'Name');
                    removeItem(attributeList, field + 'Id');

                    var value = 'imageTag '+item+'Id';
                    attributeList.push(value);

                    this.translatedOptions[value] = this.translate(field, 'fields', entityType);
                    if (link) {
                        this.translatedOptions[value] = this.translate(link, 'links', superEntityType) + '.' +
                            this.translatedOptions[value];
                    }
                } else if (fieldType === 'barcode') {
                    removeItem(attributeList, field);

                    var barcodeType = this.getMetadata().get(['entityDefs', entityType, 'fields', field, 'codeType']);
                    var value = "barcodeImage "+item+" type='"+barcodeType+"'";

                    attributeList.push(value);

                    this.translatedOptions[value] = this.translate(field, 'fields', entityType);
                    if (link) {
                        this.translatedOptions[value] = this.translate(link, 'links', superEntityType) + '.' +
                            this.translatedOptions[value];
                    }
                }
            }
        },

        setupTranslatedOptions: function () {
            var entityType = this.model.get('entityType');

            this.attributeList.forEach((item) => {
                if (~['today', 'now', 'pagebreak'].indexOf(item)) {
                    if (!this.getMetadata().get(['entityDefs', entityType, 'fields', item])) {
                        this.translatedOptions[item] = this.getLanguage()
                            .translateOption(item, 'placeholders', 'Template');

                        return;
                    }
                }

                var field = item;
                var scope = entityType;
                var isForeign = false;

                if (~item.indexOf('.')) {
                    isForeign = true;
                    field = item.split('.')[1];
                    var link = item.split('.')[0];
                    scope = this.getMetadata().get('entityDefs.' + entityType + '.links.' + link + '.entity');
                }

                if (this.translatedOptions[item]) {
                    return;
                }

                this.translatedOptions[item] = this.translate(field, 'fields', scope);

                if (field.indexOf('Id') === field.length - 2) {
                    var baseField = field.substr(0, field.length - 2);

                    if (this.getMetadata().get(['entityDefs', scope, 'fields', baseField])) {
                        this.translatedOptions[item] = this.translate(baseField, 'fields', scope) +
                            ' (' + this.translate('id', 'fields') + ')';
                    }
                }
                else if (field.indexOf('Name') === field.length - 4) {
                    var baseField = field.substr(0, field.length - 4);

                    if (this.getMetadata().get(['entityDefs', scope, 'fields', baseField])) {
                        this.translatedOptions[item] = this.translate(baseField, 'fields', scope) +
                            ' (' + this.translate('name', 'fields') + ')';
                    }
                }
                else if (field.indexOf('Type') === field.length - 4) {
                    var baseField = field.substr(0, field.length - 4);

                    if (this.getMetadata().get(['entityDefs', scope, 'fields', baseField])) {
                        this.translatedOptions[item] = this.translate(baseField, 'fields', scope) +
                            ' (' + this.translate('type', 'fields') + ')';
                    }
                }

                if (field.indexOf('Ids') === field.length - 3) {
                    var baseField = field.substr(0, field.length - 3);

                    if (this.getMetadata().get(['entityDefs', scope, 'fields', baseField])) {
                        this.translatedOptions[item] = this.translate(baseField, 'fields', scope) +
                            ' (' + this.translate('ids', 'fields') + ')';
                    }
                }
                else if (field.indexOf('Names') === field.length - 5) {
                    var baseField = field.substr(0, field.length - 5);
                    if (this.getMetadata().get(['entityDefs', scope, 'fields', baseField])) {
                        this.translatedOptions[item] = this.translate(baseField, 'fields', scope) +
                            ' (' + this.translate('names', 'fields') + ')';
                    }
                }
                else if (field.indexOf('Types') === field.length - 5) {
                    var baseField = field.substr(0, field.length - 5);

                    if (this.getMetadata().get(['entityDefs', scope, 'fields', baseField])) {
                        this.translatedOptions[item] = this.translate(baseField, 'fields', scope) +
                            ' (' + this.translate('types', 'fields') + ')';
                    }
                }

                if (isForeign) {
                    this.translatedOptions[item] =  this.translate(link, 'links', entityType) + '.' +
                        this.translatedOptions[item];
                }
            });
        },

        afterRender: function () {
            Dep.prototype.afterRender.call(this);

            if (this.mode === this.MODE_EDIT) {
                Select.init(this.$el.find('[data-name="variables"]'));
            }
        },

        fetch: function () {},

    });
});
PK]3B��%%$views/template/fields/entity-type.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/template/fields/entity-type', ['views/fields/entity-type'], function (Dep) {

    return Dep.extend({

        checkAvailability: function (entityType) {
            var defs = this.scopesMetadataDefs[entityType] || {};

            if (defs.pdfTemplate) {
                return true;
            }

            if (defs.entity && defs.object) {
                return true;
            }
        },
    });
});
PK]V�SE��"views/template/fields/font-face.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/template/fields/font-face', ['views/fields/enum'], function (Dep) {

    return Dep.extend({

        setupOptions: function () {
            var engine = this.getConfig().get('pdfEngine') || 'Tcpdf';

            var fontFaceList = this.getMetadata().get([
                'app', 'pdfEngines', engine, 'fontFaceList',
            ]) || [];

            fontFaceList = Espo.Utils.clone(fontFaceList);

            fontFaceList.unshift('');

            this.params.options = fontFaceList;
        },
    });
});
PK]��/��views/template/record/edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/template/record/edit', ['views/record/edit'], function (Dep) {

    return Dep.extend({

        saveAndContinueEditingAction: true,

        setup: function () {
            Dep.prototype.setup.call(this);

            if (!this.model.isNew()) {
                this.setFieldReadOnly('entityType');
            }

            if (this.model.get('entityType')) {
                this.showField('variables');
            }
            else {
                this.hideField('variables');
            }

            if (this.model.isNew()) {
                var storedData = {};

                this.listenTo(this.model, 'change:entityType', function (model) {
                    var entityType = this.model.get('entityType');

                    if (!entityType) {
                        this.model.set('header', '');
                        this.model.set('body', '');
                        this.model.set('footer', '');

                        this.hideField('variables');

                        return;
                    }
                    this.showField('variables');

                    if (entityType in storedData) {
                        this.model.set('header', storedData[entityType].header);
                        this.model.set('body', storedData[entityType].body);
                        this.model.set('footer', storedData[entityType].footer);

                        return;
                    }

                    var header, body, footer;

                    var sourceType = null;

                    if (
                        this.getMetadata().get(['entityDefs', 'Template', 'defaultTemplates', entityType])
                    ) {
                        var sourceType = entityType;
                    }
                    else {
                        var scopeType = this.getMetadata().get(['scopes', entityType, 'type']);

                        if (
                            scopeType &&
                            this.getMetadata().get(['entityDefs', 'Template', 'defaultTemplates', scopeType])
                        ) {

                            var sourceType = scopeType;
                        }
                    }

                    if (sourceType) {
                        header = this.getMetadata().get(
                            ['entityDefs', 'Template', 'defaultTemplates', sourceType, 'header']
                        );

                        body = this.getMetadata().get(
                            ['entityDefs', 'Template', 'defaultTemplates', sourceType, 'body']
                        );

                        footer = this.getMetadata().get(
                            ['entityDefs', 'Template', 'defaultTemplates', sourceType, 'footer']
                        );
                    }

                    body = body || '';
                    header = header || null;
                    footer = footer || null;

                    this.model.set('body', body);
                    this.model.set('header', header);
                    this.model.set('footer', footer);
                }, this);

                this.listenTo(this.model, 'change', function (e, o) {
                    if (!o.ui) {
                        return;
                    }

                    if (
                        !this.model.hasChanged('header') &&
                        !this.model.hasChanged('body') &&
                        !this.model.hasChanged('footer')
                    ) {
                        return;
                    }

                    var entityType = this.model.get('entityType');

                    if (!entityType) {
                        return;
                    }

                    storedData[entityType] = {
                        header: this.model.get('header'),
                        body: this.model.get('body'),
                        footer: this.model.get('footer'),
                    };
                }, this);
            }
        },

    });
});
PK]�aT��views/template/record/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/template/record/detail', ['views/record/detail'], function (Dep) {

    return Dep.extend({

        saveAndContinueEditingAction: true,

        setup: function () {
            Dep.prototype.setup.call(this);

            this.hideField('variables');

            this.on('after:set-edit-mode', function () {
                this.showField('variables');
            }, this);

            this.on('after:set-detail-mode', function () {
                this.hideField('variables');
            }, this);
        },

    });
});
PK]���mm(views/outbound-email/fields/test-send.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/outbound-email/fields/test-send', ['views/fields/base'], function (Dep) {

    return Dep.extend({

        templateContent:
            '<button class="btn btn-default hidden" data-action="sendTestEmail">'+
            '{{translate \'Send Test Email\' scope=\'Email\'}}</button>',

        events: {
            'click [data-action="sendTestEmail"]': function () {
                this.send();
            },
        },

        fetch: function () {
            return {};
        },

        checkAvailability: function () {
            if (this.model.get('smtpServer')) {
                this.$el.find('button').removeClass('hidden');
            } else {
                this.$el.find('button').addClass('hidden');
            }
        },

        afterRender: function () {
            this.checkAvailability();

            this.stopListening(this.model, 'change:smtpServer');

            this.listenTo(this.model, 'change:smtpServer', () => {
                this.checkAvailability();
            });
        },

        getSmtpData: function () {
            return {
                'server': this.model.get('smtpServer'),
                'port': this.model.get('smtpPort'),
                'auth': this.model.get('smtpAuth'),
                'security': this.model.get('smtpSecurity'),
                'username': this.model.get('smtpUsername'),
                'password': this.model.get('smtpPassword') || null,
                'fromName': this.model.get('outboundEmailFromName'),
                'fromAddress': this.model.get('outboundEmailFromAddress'),
                'type': 'outboundEmail',
            };
        },

        send: function () {
            var data = this.getSmtpData();

            this.createView('popup', 'views/outbound-email/modals/test-send', {
                emailAddress: this.getUser().get('emailAddress')
            }, (view) => {
                view.render();

                this.listenToOnce(view, 'send', (emailAddress) => {
                    this.$el.find('button').addClass('disabled');
                    data.emailAddress = emailAddress;

                    this.notify('Sending...');

                    view.close();

                    Espo.Ajax.postRequest('Email/sendTest', data)
                        .then(() => {
                            this.$el.find('button').removeClass('disabled');

                            Espo.Ui.success(this.translate('testEmailSent', 'messages', 'Email'));
                        })
                        .catch((xhr) => {
                            var reason = xhr.getResponseHeader('X-Status-Reason') || '';

                            reason = reason
                                .replace(/ $/, '')
                                .replace(/,$/, '');

                            var msg = this.translate('Error');

                            if (xhr.status !== 200) {
                                msg += ' ' + xhr.status;
                            }

                            if (xhr.responseText) {
                                try {
                                    var data = JSON.parse(xhr.responseText);

                                    reason = data.message || reason;
                                }
                                catch (e) {
                                    console.error('Could not parse error response body.');

                                    return;
                                }
                            }

                            if (reason) {
                                msg += ': ' + reason;
                            }

                            Espo.Ui.error(msg, true);

                            console.error(msg);

                            xhr.errorIsHandled = true;

                            this.$el.find('button').removeClass('disabled');
                        }
                    );

                });
            });
        },
    });
});
PK]rIZ��
�
(views/outbound-email/modals/test-send.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/outbound-email/modals/test-send', ['views/modal'], function (Dep) {

    return Dep.extend({

        cssName: 'test-send',

        templateContent: `
            <label class="control-label">{{translate \'Email Address\' scope=\'Email\'}}</label>
            <input type="text" name="emailAddress" value="{{emailAddress}}" class="form-control">
        `,

        data: function () {
            return {
                emailAddress: this.options.emailAddress,
            };
        },

        setup: function () {
            this.buttonList = [
                {
                    name: 'send',
                    text: this.translate('Send', 'labels', 'Email'),
                    style: 'primary',
                    onClick: () => {
                        var emailAddress = this.$el.find('input').val();

                        if (emailAddress === '') {
                            return;
                        }

                        this.trigger('send', emailAddress);
                    },
                },
                {
                    name: 'cancel',
                    label: 'Cancel',
                    onClick: dialog =>{
                        dialog.close();
                    },
                }
            ];
        },
    });
});
PK]6��\��views/login-second-step.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';
import Base64 from 'js-base64';
import $ from 'jquery';

class LoginSecondStepView extends View {

    /** @inheritDoc */
    template = 'login-second-step'

    /** @inheritDoc */
    views =  {
        footer: {
            fullSelector: 'body > footer',
            view: 'views/site/footer',
        },
    }

    /**
     * @type {string|null}
     * @private
     */
    anotherUser = null

    /**
     * Response from the first step.
     *
     * @type {Object.<string, *>}
     * @private
     */
    loginData =  null

    /**
     * Headers composed in the first step.
     *
     * @type {Object.<string, string>}
     * @private
     */
    headers =  null

    /** @private */
    isPopoverDestroyed =  false

    /** @inheritDoc */
    events = {
        /** @this LoginSecondStepView */
        'submit #login-form': function (e) {
            e.preventDefault();

            this.send();
        },
        /** @this LoginSecondStepView */
        'click [data-action="backToLogin"]': function () {
            this.trigger('back');
        },
        /** @this LoginSecondStepView */
        'keydown': function (e) {
            if (Espo.Utils.getKeyFromKeyEvent(e) === 'Control+Enter') {
                e.preventDefault();

                this.send();
            }
        },
    }

    /** @inheritDoc */
    data() {
        return {
            message: this.message,
        };
    }

    /** @inheritDoc */
    setup() {
        this.message = this.translate(this.options.loginData.message, 'messages', 'User');
        this.anotherUser = this.options.anotherUser || null;
        this.headers = this.options.headers || {};
        this.loginData = this.options.loginData;
    }

    /** @inheritDoc */
    afterRender() {
        this.$code = $('[data-name="field-code"]');
        this.$submit = this.$el.find('#btn-send');

        this.$code.focus();
    }

    /** @private */
    send() {
        let code = this.$code.val().trim().replace(/\s/g, '');

        let userName = this.options.userName;
        let token = this.loginData.token;
        let headers = Espo.Utils.clone(this.headers);

        if (code === '') {
            this.processEmptyCode();

            return;
        }

        this.disableForm();

        if (userName && token) {
            let authString = Base64.encode(userName  + ':' + token);

            headers['Authorization'] = 'Basic ' + authString;
            headers['Espo-Authorization'] = authString;
        }

        headers['Espo-Authorization-Code'] = code;
        headers['Espo-Authorization-Create-Token-Secret'] = 'true';

        if (this.anotherUser !== null) {
            headers['X-Another-User'] = this.anotherUser;
        }

        this.notifyLoading();

        Espo.Ajax
            .getRequest('App/user', null, {
                login: true,
                headers: headers,
            })
            .then(data => {
                Espo.Ui.notify(false);

                this.triggerLogin(userName, data);
            })
            .catch(xhr => {
                this.undisableForm();

                if (xhr.status === 401) {
                    this.onWrongCredentials();
                }
            });
    }

    /**
     * Trigger login to proceed to the application.
     *
     * @private
     * @param {string} userName A username.
     * @param {Object.<string, *>} data Data returned from the `App/user` request.
     */
    triggerLogin(userName, data) {
        if (this.anotherUser) {
            data.anotherUser = this.anotherUser;
        }

        if (!userName) {
            userName = (data.user || {}).userName;
        }

        this.trigger('login', userName, data);
    }

    /** @private */
    processEmptyCode() {
        this.isPopoverDestroyed = false;

        let message = this.getLanguage().translate('codeIsRequired', 'messages', 'User');

        let $el = this.$code;

        $el
            .popover({
                placement: 'bottom',
                container: 'body',
                content: message,
                trigger: 'manual',
            })
            .popover('show');

        let $cell = $el.closest('.form-group');

        $cell.addClass('has-error');

        $el.one('mousedown click', () => {
            $cell.removeClass('has-error');

            if (this.isPopoverDestroyed) {
                return;
            }

            $el.popover('destroy');

            this.isPopoverDestroyed = true;
        });
    }

    /** @private */
    onWrongCredentials() {
        let $cell = $('#login .form-group');

        $cell.addClass('has-error');

        this.$el.one('mousedown click', () => {
            $cell.removeClass('has-error');
        });

        Espo.Ui.error(this.translate('wrongCode', 'messages', 'User'));
    }

    /** @private */
    notifyLoading() {
        Espo.Ui.notify(' ... ');
    }

    /** @private */
    disableForm() {
        this.$submit.addClass('disabled').attr('disabled', 'disabled');
    }

    /** @private */
    undisableForm() {
        this.$submit.removeClass('disabled').removeAttr('disabled');
    }
}

// noinspection JSUnusedGlobalSymbols
export default LoginSecondStepView;
PK]bb0views/site-portal/master.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/site-portal/master', ['views/site/master'], function (Dep) {

    return Dep.extend({

        template: 'site/master',

        views: {
            header: {
                id: 'header',
                view: 'views/site-portal/header'
            },
            main: {
                id: 'main',
                view: false,
            },
            footer: {
                fullSelector: 'body > footer',
                view: 'views/site/footer'
            }
        },

        afterRender: function () {
            Dep.prototype.afterRender.call(this);
            this.$el.find('#main').addClass('main-portal');
        },

    });
});
PK]�bZ�}}views/site-portal/header.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/site-portal/header', ['views/site/header'], function (Dep) {

    return Dep.extend({

        template: 'site/header',

        navbarView: 'views/site-portal/navbar',

        customViewPath: ['clientDefs', 'App', 'portalNavbarView'],

    });
});
PK]LBC?	?	views/site-portal/navbar.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/site-portal/navbar', ['views/site/navbar'], function (Dep) {

    return Dep.extend({

        getLogoSrc: function () {
            var companyLogoId = this.getConfig().get('companyLogoId');
            if (!companyLogoId) {
                return this.getBasePath() + (this.getThemeManager().getParam('logo') || 'client/img/logo.svg');
            }
            return this.getBasePath() + '?entryPoint=LogoImage&id='+companyLogoId+'&t=' + companyLogoId;
        },

        getTabList: function () {
            var tabList = this.getConfig().get('tabList') || [];
            tabList = Espo.Utils.clone(tabList || []);

            if (this.getThemeManager().getParam('navbarIsVertical') || tabList.length) {
                tabList.unshift('Home');
            }
            return tabList;
        },

        getQuickCreateList: function () {
            return this.getConfig().get('quickCreateList') || []
        }

    });

});
PK]�Z�L"views/dashboard-template/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/dashboard-template/detail', ['views/detail'], function (Dep) {

    return Dep.extend({

        actionDeployToUsers: function () {
            this.createView('dialog', 'views/dashboard-template/modals/deploy-to-users', {
                model: this.model,
            }, function (view) {
                view.render();
            }, this);
        },

        actionDeployToTeam: function () {
            this.createView('dialog', 'views/dashboard-template/modals/deploy-to-team', {
                model: this.model,
            }, function (view) {
                view.render();
            }, this);
        },
    });
});
PK]2x�'views/dashboard-template/record/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/dashboard-template/record/list', ['views/record/list'], function (Dep) {

    return Dep.extend({

        massActionList: ['remove', 'export'],
    });
});
PK]p�`H��1views/dashboard-template/modals/deploy-to-team.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/dashboard-template/modals/deploy-to-team', ['views/modal', 'model'], function (Dep, Model) {

    return Dep.extend({

        className: 'dialog dialog-record',

        templateContent: '<div class="record">{{{record}}}</div>',

        setup: function () {
            this.buttonList = [
                {
                    name: 'deploy',
                    text: this.translate('Deploy for Team', 'labels', 'DashboardTemplate'),
                    style: 'danger',
                },
                {
                    name: 'cancel',
                    label: 'Cancel',
                },
            ];

            this.headerText = this.model.get('name');

            this.formModel = new Model();
            this.formModel.name = 'None';

            this.formModel.setDefs({
                fields: {
                    'team': {
                        type: 'link',
                        entity: 'Team',
                        required: true
                    },
                    'append': {
                        type: 'bool'
                    },
                }
            });

            this.createView('record', 'views/record/edit-for-modal', {
                scope: 'None',
                model: this.formModel,
                selector: '.record',
                detailLayout: [
                    {
                        rows: [
                            [
                                {
                                    name: 'team',
                                    labelText: this.translate('team', 'links'),
                                },
                                {
                                    name: 'append',
                                    labelText: this.translate('append', 'fields', 'DashboardTemplate'),
                                },
                            ]
                        ]
                    }
                ],
            });
        },

        actionDeploy: function () {
            if (this.getView('record').processFetch()) {
                Espo.Ajax
                    .postRequest('DashboardTemplate/action/deployToTeam', {
                        id: this.model.id,
                        teamId: this.formModel.get('teamId'),
                        append: this.formModel.get('append'),
                    })
                    .then(() => {
                        Espo.Ui.success(this.translate('Done'));
                        this.close();
                    });
            }
        },
    });
});
PK]����2views/dashboard-template/modals/deploy-to-users.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/dashboard-template/modals/deploy-to-users', ['views/modal', 'model'], function (Dep, Model) {

    return Dep.extend({

        className: 'dialog dialog-record',

        templateContent: '<div class="record">{{{record}}}</div>',

        setup: function () {
            this.buttonList = [
                {
                    name: 'deploy',
                    text: this.translate('Deploy for Users', 'labels', 'DashboardTemplate'),
                    style: 'danger',
                },
                {
                    name: 'cancel',
                    label: 'Cancel',
                },
            ];

            this.headerText = this.model.get('name');

            this.formModel = new Model();
            this.formModel.name = 'None';

            this.formModel.setDefs({
                fields: {
                    'users': {
                        type: 'linkMultiple',
                        view: 'views/fields/users',
                        entity: 'User',
                        required: true
                    },
                    'append': {
                        type: 'bool'
                    },
                }
            });

            this.createView('record', 'views/record/edit-for-modal', {
                scope: 'None',
                model: this.formModel,
                selector: '.record',
                detailLayout: [
                    {
                        rows: [
                            [
                                {
                                    name: 'users',
                                    labelText: this.translate('users', 'links'),
                                },
                                {
                                    name: 'append',
                                    labelText: this.translate('append', 'fields', 'DashboardTemplate'),
                                }
                            ]
                        ]
                    }
                ],
            });
        },

        actionDeploy: function () {
            if (this.getView('record').processFetch()) {
                Espo.Ajax
                    .postRequest('DashboardTemplate/action/deployToUsers', {
                        id: this.model.id,
                        userIdList: this.formModel.get('usersIds'),
                        append: this.formModel.get('append'),
                    })
                    .then(() => {
                        Espo.Ui.success(this.translate('Done'));
                        this.close();
                    });
            }
        },
    });
});
PK]�.��TTviews/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module module:views/detail */

import MainView from 'views/main';

/**
 * A detail view.
 */
class DetailView extends MainView {

    /** @inheritDoc */
    template = 'detail'
    /** @inheritDoc */
    name = 'Detail'

    /** @inheritDoc */
    optionsToPass = [
        'attributes',
        'returnUrl',
        'returnDispatchParams',
        'rootUrl',
    ]

    /**
     * A header view name.
     *
     * @type {string}
     */
    headerView = 'views/header'

    /**
     * A record view name.
     *
     * @type {string}
     */
    recordView = 'views/record/detail'

    /**
     * A root breadcrumb item not to be a link.
     *
     * @type {boolean}
     */
    rootLinkDisabled = false

    /**
     * A root URL.
     *
     * @type {string}
     */
    rootUrl = ''

    /**
     * Is return.
     *
     * @protected
     */
    isReturn = false

    /** @inheritDoc */
    shortcutKeys = {}

    /**
     * An entity type.
     *
     * @type {string}
     */
    entityType

    /** @inheritDoc */
    setup() {
        super.setup();

        this.entityType = this.model.entityType || this.model.name;

        this.headerView = this.options.headerView || this.headerView;
        this.recordView = this.options.recordView || this.recordView;

        this.rootUrl = this.options.rootUrl || this.options.params.rootUrl || '#' + this.scope;
        this.isReturn = this.options.isReturn || this.options.params.isReturn || false;

        this.setupHeader();
        this.setupRecord();
        this.setupPageTitle();
        this.initFollowButtons();
        this.initRedirect();
    }

    /** @inheritDoc */
    setupFinal() {
        super.setupFinal();

        this.wait(
            this.getHelper().processSetupHandlers(this, 'detail')
        );
    }

    /** @private */
    initRedirect() {
        if (!this.options.params.isAfterCreate) {
            return;
        }

        let redirect = () => {
            Espo.Ui.success(this.translate('Created'));

            setTimeout(() => {
                this.getRouter().navigate(this.rootUrl, {trigger: true});
            }, 1000)
        };

        if (
            this.model.lastSyncPromise &&
            this.model.lastSyncPromise.getStatus() === 403
        ) {
            redirect();

            return;
        }

        this.listenToOnce(this.model, 'fetch-forbidden', () => redirect())
    }

    /**
     * Set up a page title.
     */
    setupPageTitle() {
        this.listenTo(this.model, 'after:save', () => {
            this.updatePageTitle();
        });

        this.listenTo(this.model, 'sync', (model) => {
            if (model && model.hasChanged('name')) {
                this.updatePageTitle();
            }
        });
    }

    /**
     * Set up a header.
     */
    setupHeader() {
        this.createView('header', this.headerView, {
            model: this.model,
            fullSelector: '#main > .header',
            scope: this.scope,
            fontSizeFlexible: true,
        });

        this.listenTo(this.model, 'sync', (model) => {
            if (model && model.hasChanged('name')) {
                if (this.getView('header')) {
                    this.getView('header').reRender();
                }
            }
        });
    }

    /**
     * Set up a record.
     */
    setupRecord() {
        let o = {
            model: this.model,
            fullSelector: '#main > .record',
            scope: this.scope,
            shortcutKeysEnabled: true,
            isReturn: this.isReturn,
        };

        this.optionsToPass.forEach((option) => {
            o[option] = this.options[option];
        });

        if (this.options.params && this.options.params.rootUrl) {
            o.rootUrl = this.options.params.rootUrl;
        }

        if (this.model.get('deleted')) {
            o.readOnly = true;
        }

        return this.createView('record', this.getRecordViewName(), o);
    }

    /**
     * Get a record view name.
     *
     * @returns {string}
     */
    getRecordViewName() {
        return this.getMetadata()
            .get('clientDefs.' + this.scope + '.recordViews.detail') || this.recordView;
    }

    /** @private */
    initFollowButtons() {
        if (!this.getMetadata().get(['scopes', this.scope, 'stream'])) {
            return;
        }

        this.addFollowButtons();

        this.listenTo(this.model, 'change:isFollowed', () => {
            this.controlFollowButtons();
        });
    }

    /** @private */
    addFollowButtons() {
        let isFollowed = this.model.get('isFollowed');

        this.addMenuItem('buttons', {
            name: 'unfollow',
            label: 'Followed',
            style: 'success',
            action: 'unfollow',
            hidden: !isFollowed,
        }, true);

        this.addMenuItem('buttons', {
            name: 'follow',
            label: 'Follow',
            style: 'default',
            iconHtml: '<span class="fas fa-rss fa-sm"></span>',
            text: this.translate('Follow'),
            action: 'follow',
            hidden: isFollowed ||
                !this.model.has('isFollowed') ||
                !this.getAcl().checkModel(this.model, 'stream'),
        }, true);
    }

    /** @private */
    controlFollowButtons() {
        let isFollowed = this.model.get('isFollowed');

        if (isFollowed) {
            this.hideHeaderActionItem('follow');
            this.showHeaderActionItem('unfollow');

            return;
        }

        this.hideHeaderActionItem('unfollow');

        if (this.getAcl().checkModel(this.model, 'stream')) {
            this.showHeaderActionItem('follow');
        }
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * Action 'follow'.
     */
    actionFollow() {
        this.disableMenuItem('follow');

        Espo.Ajax
            .putRequest(this.entityType + '/' + this.model.id + '/subscription')
            .then(() => {
                this.hideHeaderActionItem('follow');

                this.model.set('isFollowed', true, {sync: true});

                this.enableMenuItem('follow');
            })
            .catch(() => {
                this.enableMenuItem('follow');
            });
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * Action 'unfollow'.
     */
    actionUnfollow() {
        this.disableMenuItem('unfollow');

        Espo.Ajax
            .deleteRequest(this.entityType + '/' + this.model.id + '/subscription')
            .then(() => {
                this.hideHeaderActionItem('unfollow');

                this.model.set('isFollowed', false, {sync: true});

                this.enableMenuItem('unfollow');
            })
            .catch(() => {
                this.enableMenuItem('unfollow');
            });
    }

    /**
     * @inheritDoc
     */
    getHeader() {
        let name = this.model.get('name') || this.model.id;

        let $name =
            $('<span>')
                .addClass('font-size-flexible title')
                .text(name);

        if (this.model.get('deleted')) {
            $name.css('text-decoration', 'line-through');
        }

        let headerIconHtml = this.getHeaderIconHtml();
        let scopeLabel = this.getLanguage().translate(this.scope, 'scopeNamesPlural');

        let $root = $('<span>').text(scopeLabel);

        if (!this.rootLinkDisabled) {
            $root = $('<span>')
                .append(
                    $('<a>')
                        .attr('href', this.rootUrl)
                        .addClass('action')
                        .attr('data-action', 'navigateToRoot')
                        .text(scopeLabel)
                );
        }

        if (headerIconHtml) {
            $root.prepend(headerIconHtml);
        }

        return this.buildHeaderHtml([
            $root,
            $name,
        ]);
    }

    /**
     * @inheritDoc
     */
    updatePageTitle() {
        if (this.model.has('name')) {
            this.setPageTitle(this.model.get('name') || this.model.id);

            return;
        }

        super.updatePageTitle();
    }

    /**
     * @return {module:views/record/detail}
     */
    getRecordView() {
        return this.getView('record');
    }

    /**
     * Update a relationship panel (fetch data).
     *
     * @param {string} name A relationship name.
     */
    updateRelationshipPanel(name) {
        let bottom = this.getView('record').getView('bottom');

        if (bottom) {
            let rel = bottom.getView(name);

            if (rel) {
                rel.collection.fetch();
            }
        }
    }

    /**
     * @deprecated Use metadata clientDefs > {EntityType} > relationshipPanels > {link} > createAttributeMap.
     * @type {Object}
     */
    relatedAttributeMap = {}

    /**
     * @deprecated Use clientDefs > {EntityType} > relationshipPanels > {link} > createHandler.
     * @type {Object}
     */
    relatedAttributeFunctions = {}

    /**
     * @deprecated Use clientDefs > {EntityType} > relationshipPanels > {link} > selectHandler.
     * @type {Object}
     */
    selectRelatedFilters = {}

    /**
     * @deprecated Use clientDefs > {EntityType} > relationshipPanels > {link} > selectHandler or
     *  clientDefs > {EntityType} > relationshipPanels > {link} > selectPrimaryFilter.
     * @type {Object}
     */
    selectPrimaryFilterNames = {}

    /**
     * @deprecated Use clientDefs > {EntityType} > relationshipPanels > {link} > selectHandler or
     *  clientDefs > {EntityType} > relationshipPanels > {link} > selectBoolFilterList.
     * @type {Object}
     */
    selectBoolFilterLists = []

    /**
     * Action 'createRelated'.
     *
     * @param {Object} data
     */
    actionCreateRelated(data) {
        data = data || {};

        let link = data.link;
        let scope = this.model.defs['links'][link].entity;
        let foreignLink = this.model.defs['links'][link].foreign;

        let attributes = {};

        if (
            this.relatedAttributeFunctions[link] &&
            typeof this.relatedAttributeFunctions[link] === 'function'
        ) {
            attributes = _.extend(this.relatedAttributeFunctions[link].call(this), attributes);
        }

        let attributeMap = this.getMetadata()
            .get(['clientDefs', this.scope, 'relationshipPanels', link, 'createAttributeMap']) ||
            this.relatedAttributeMap[link] || {};

        Object.keys(attributeMap)
            .forEach(attr => {
                attributes[attributeMap[attr]] = this.model.get(attr);
            });

        Espo.Ui.notify(' ... ');

        let handler = this.getMetadata()
            .get(['clientDefs', this.scope, 'relationshipPanels', link, 'createHandler']);

        new Promise(resolve => {
            if (!handler) {
                resolve({});

                return;
            }

            Espo.loader.requirePromise(handler)
                .then(Handler => new Handler(this.getHelper()))
                .then(handler => {
                    handler.getAttributes(this.model)
                        .then(attributes => resolve(attributes));
                });
        }).then(additionalAttributes => {
            attributes = {...attributes, ...additionalAttributes};

            let viewName = this.getMetadata()
                .get(['clientDefs', scope, 'modalViews', 'edit']) || 'views/modals/edit';

            this.createView('quickCreate', viewName, {
                scope: scope,
                relate: {
                    model: this.model,
                    link: foreignLink,
                },
                attributes: attributes,
            }, view => {
                view.render();
                view.notify(false);

                this.listenToOnce(view, 'after:save', () => {
                    if (data.fromSelectRelated) {
                        setTimeout(() => this.clearView('dialogSelectRelated'), 25);
                    }

                    this.updateRelationshipPanel(link);

                    this.model.trigger('after:relate');
                    this.model.trigger('after:relate:' + link);
                });
            });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * Action 'selectRelated'.
     *
     * @param {Object.<string, *>} data
     */
    actionSelectRelated(data) {
        let link = data.link;

        if (!data.foreignEntityType && !this.model.defs['links'][link]) {
            throw new Error('Link ' + link + ' does not exist.');
        }

        let scope = data.foreignEntityType || this.model.defs['links'][link].entity;
        let massRelateEnabled = data.massSelect;

        /** @var {Object.<string, *>} */
        let panelDefs = this.getMetadata().get(['clientDefs', this.scope, 'relationshipPanels', link]) || {};

        let advanced = {};

        if (link in this.selectRelatedFilters) {
            advanced = Espo.Utils.cloneDeep(this.selectRelatedFilters[link]) || advanced;

            for (let filterName in advanced) {
                if (typeof advanced[filterName] === 'function') {
                    let filtersData = advanced[filterName].call(this);

                    if (filtersData) {
                        advanced[filterName] = filtersData;
                    } else {
                        delete advanced[filterName];
                    }
                }
            }
        }

        let foreignLink = this.model.getLinkParam(link, 'foreign');

        if (foreignLink && scope) {
            // Select only records not related with any.
            let foreignLinkType = this.getMetadata()
                .get(['entityDefs', scope, 'links', foreignLink, 'type']);
            let foreignLinkFieldType = this.getMetadata()
                .get(['entityDefs', scope, 'fields', foreignLink, 'type']);

            if (
                ~['belongsTo', 'belongsToParent'].indexOf(foreignLinkType) &&
                foreignLinkFieldType &&
                !advanced[foreignLink] &&
                ~['link', 'linkParent'].indexOf(foreignLinkFieldType)
            ) {
                advanced[foreignLink] = {
                    type: 'isNull',
                    attribute: foreignLink + 'Id',
                    data: {
                        type: 'isEmpty',
                    },
                };
            }
        }

        let primaryFilterName = data.primaryFilterName || this.selectPrimaryFilterNames[link] || null;

        if (typeof primaryFilterName === 'function') {
            primaryFilterName = primaryFilterName.call(this);
        }

        let dataBoolFilterList = data.boolFilterList;

        if (typeof data.boolFilterList === 'string') {
            dataBoolFilterList = data.boolFilterList.split(',');
        }

        let boolFilterList = dataBoolFilterList ||
            panelDefs.selectBoolFilterList ||
            this.selectBoolFilterLists[link];

        if (typeof boolFilterList === 'function') {
            boolFilterList = boolFilterList.call(this);
        }

        boolFilterList = Espo.Utils.clone(boolFilterList);

        primaryFilterName = primaryFilterName || panelDefs.selectPrimaryFilterName || null;

        let viewKey = data.viewKey || 'select';

        let viewName = panelDefs.selectModalView ||
            this.getMetadata().get(['clientDefs', scope, 'modalViews', viewKey]) ||
            'views/modals/select-records';

        Espo.Ui.notify(' ... ');

        let handler = panelDefs.selectHandler || null;

        new Promise(resolve => {
            if (!handler) {
                resolve({});

                return;
            }

            Espo.loader.requirePromise(handler)
                .then(Handler => new Handler(this.getHelper()))
                .then(/** module:handlers/select-related */handler => {
                    handler.getFilters(this.model)
                        .then(filters => resolve(filters));
                });
        }).then(filters => {
            advanced = {...advanced, ...(filters.advanced || {})};

            if (boolFilterList || filters.bool) {
                boolFilterList = [
                    ...(boolFilterList || []),
                    ...(filters.bool || []),
                ];
            }

            if (filters.primary && !primaryFilterName) {
                primaryFilterName = filters.primary;
            }

            this.createView('dialogSelectRelated', viewName, {
                scope: scope,
                multiple: true,
                createButton: data.createButton || false,
                triggerCreateEvent: true,
                filters: advanced,
                massRelateEnabled: massRelateEnabled,
                primaryFilterName: primaryFilterName,
                boolFilterList: boolFilterList,
                mandatorySelectAttributeList: panelDefs.selectMandatoryAttributeList,
                layoutName: panelDefs.selectLayout,
            }, dialog => {
                dialog.render();

                Espo.Ui.notify(false);

                this.listenTo(dialog, 'create', () => {
                    this.actionCreateRelated({
                        link: data.link,
                        fromSelectRelated: true,
                    });
                });

                this.listenToOnce(dialog, 'select', (selectObj) => {
                    let data = {};

                    if (Object.prototype.toString.call(selectObj) === '[object Array]') {
                        let ids = [];

                        selectObj.forEach(model => ids.push(model.id));

                        data.ids = ids;
                    }
                    else {
                        if (selectObj.massRelate) {
                            data.massRelate = true;
                            data.where = selectObj.where;
                            data.searchParams = selectObj.searchParams;
                        }
                        else {
                            data.id = selectObj.id;
                        }
                    }

                    let url = this.scope + '/' + this.model.id + '/' + link;

                    Espo.Ajax.postRequest(url, data)
                        .then(() => {
                            Espo.Ui.success(this.translate('Linked'))

                            this.updateRelationshipPanel(link);

                            this.model.trigger('after:relate');
                            this.model.trigger('after:relate:' + link);
                        });
                });
            });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * Action 'duplicate'.
     */
    actionDuplicate() {
        Espo.Ui.notify(' ... ');

        Espo.Ajax
            .postRequest(this.scope + '/action/getDuplicateAttributes', {id: this.model.id})
            .then(attributes => {
                Espo.Ui.notify(false);

                let url = '#' + this.scope + '/create';

                this.getRouter().dispatch(this.scope, 'create', {
                    attributes: attributes,
                    returnUrl: this.getRouter().getCurrentUrl(),
                    options: {
                        duplicateSourceId: this.model.id,
                    },
                });

                this.getRouter().navigate(url, {trigger: false});
            });
    }
}

export default DetailView;
PK]��ڧ�!�!views/record/detail-bottom.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/record/detail-bottom */

import PanelsContainerRecordView from 'views/record/panels-container';

/**
 * A detail-bottom record view.
 */
class DetailBottomRecordView extends PanelsContainerRecordView {

    /** @inheritDoc */
    template = 'record/bottom'

    /** @inheritDoc */
    mode = 'detail'
    streamPanel = true
    relationshipPanels = true
    readOnly = false
    portalLayoutDisabled = false
    name = 'bottom'

    /** @inheritDoc */
    setupPanels() {
        let scope = this.scope;

        this.panelList = Espo.Utils.clone(
            this.getMetadata()
                .get(['clientDefs', scope, 'bottomPanels', this.type]) || this.panelList || []);

        this.panelList.forEach(item => {
            if ('index' in item) {
                return;
            }

            if ('order' in item) {
                item.index = item.order;
            }
        });

        if (this.streamPanel && this.getMetadata().get(['scopes', scope, 'stream'])) {
            this.setupStreamPanel();
        }
    }

    /**
     * Set up a stream panel.
     */
    setupStreamPanel() {
        let streamAllowed = this.getAcl().checkModel(this.model, 'stream', true);

        if (streamAllowed === null) {
            this.listenToOnce(this.model, 'sync', () => {
                streamAllowed = this.getAcl().checkModel(this.model, 'stream', true);

                if (streamAllowed) {
                    this.onPanelsReady(() => {
                        this.showPanel('stream', 'acl');
                    });
                }
            });
        }

        if (streamAllowed !== false) {
            this.panelList.push({
                name: 'stream',
                label: 'Stream',
                view: this.getMetadata().get(['clientDefs', this.scope, 'streamPanelView']) || 'views/stream/panel',
                sticked: true,
                hidden: !streamAllowed,
                index: 2,
            });

            if (!streamAllowed) {
                this.recordHelper.setPanelStateParam('stream', 'hiddenAclLocked', true);
            }
        }
    }

    init() {
        this.recordHelper = this.options.recordHelper;
        this.scope = this.entityType = this.model.name;

        this.readOnlyLocked = this.options.readOnlyLocked || this.readOnly;
        this.readOnly = this.options.readOnly || this.readOnly;
        this.inlineEditDisabled = this.options.inlineEditDisabled || this.inlineEditDisabled;

        this.portalLayoutDisabled = this.options.portalLayoutDisabled || this.portalLayoutDisabled;

        this.recordViewObject = this.options.recordViewObject;
    }

    setup() {
        this.type = this.mode;

        if ('type' in this.options) {
            this.type = this.options.type;
        }

        this.panelList = [];

        this.setupPanels();

        this.wait(true);

        Promise.all([
            new Promise(resolve => {
                this.getHelper().layoutManager.get(
                    this.scope,
                    'bottomPanels' + Espo.Utils.upperCaseFirst(this.type),
                    (layoutData) => {
                        this.layoutData = layoutData;

                        resolve();
                    }
                );
            })
        ]).then(() => {
            let panelNameList = [];

            this.panelList = this.panelList.filter(p => {
                panelNameList.push(p.name);

                if (p.aclScope) {
                    if (!this.getAcl().checkScope(p.aclScope)) {
                        return;
                    }
                }

                if (p.accessDataList) {
                    if (!Espo.Utils.checkAccessDataList(p.accessDataList, this.getAcl(), this.getUser())) {
                        return false;
                    }
                }

                return true;
            });

            if (this.relationshipPanels) {
                let linkDefs = (this.model.defs || {}).links || {};

                if (this.layoutData) {
                    for (let name in this.layoutData) {
                        if (!linkDefs[name]) {
                            continue;
                        }

                        let p = this.layoutData[name];

                        if (!~panelNameList.indexOf(name) && !p.disabled) {
                            this.addRelationshipPanel(name, p);
                        }
                    }
                }
            }

            this.panelList = this.panelList.map((p) => {
                let item = Espo.Utils.clone(p);

                if (this.recordHelper.getPanelStateParam(p.name, 'hidden') !== null) {
                    item.hidden = this.recordHelper.getPanelStateParam(p.name, 'hidden');
                }
                else {
                    this.recordHelper.setPanelStateParam(p.name, 'hidden', item.hidden || false);
                }

                return item;
            });

            this.panelList.forEach((item) => {
                item.actionsViewKey = item.name + 'Actions';
            });

            this.alterPanels();
            this.setupPanelsFinal();
            this.setupPanelViews();

            this.wait(false);
        });
    }

    /**
     * Set read-only.
     */
    setReadOnly() {
        this.readOnly = true;
    }

    /** @private */
    addRelationshipPanel(name, item) {
        let scope = this.scope;
        let scopesDefs = this.getMetadata().get('scopes') || {};

        let p;

        if (typeof item === 'string' || item instanceof String) {
            p = {name: item};
        }
        else {
            p = Espo.Utils.clone(item || {});
        }

        p.name = p.name || name;
        if (!p.name) {
            return;
        }

        if (typeof p.order === 'undefined') p.order = 5;

        name = p.name;

        let links = (this.model.defs || {}).links || {};

        if (!(name in links)) {
            return;
        }

        let foreignScope = links[name].entity;

        if ((scopesDefs[foreignScope] || {}).disabled) {
            return;
        }

        if (!this.getAcl().check(foreignScope, 'read')) {
            return;
        }

        let defs = this.getMetadata().get(['clientDefs', scope, 'relationshipPanels', name]) || {};
        defs = Espo.Utils.clone(defs);

        for (let i in defs) {
            if (i in p) {
                continue;
            }

            p[i] = defs[i];
        }

        if (!p.view) {
            p.view = 'views/record/panels/relationship';
        }

        if (this.recordHelper.getPanelStateParam(p.name, 'hidden') !== null) {
            p.hidden = this.recordHelper.getPanelStateParam(p.name, 'hidden');
        }
        else {
            this.recordHelper.setPanelStateParam(p.name, 'hidden', p.hidden || false);
        }

        this.panelList.push(p);
    }
}

export default DetailBottomRecordView;
PK]�,�

views/record/edit-for-modal.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/record/edit-for-modal */

import EditRecordView from 'views/record/edit';

/**
 * An edit-record view to used for custom forms.
 */
class EditForModalRecordView extends EditRecordView {

    bottomView = null
    sideView = null
    buttonsDisabled = true
    isWide = true
    accessControlDisabled = true
    confirmLeaveDisabled = true
}

export default EditForModalRecordView;
PK]I��%�%views/record/list-tree.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/record/list-tree */

import ListRecordView from 'views/record/list';

class ListTreeRecordView extends ListRecordView {

    template = 'record/list-tree'

    showMore = false
    showCount = false
    checkboxes = false
    rowActionsView = false
    presentationType = 'tree'
    header = false
    listContainerEl = ' > .list > ul'
    checkAllResultDisabled = true
    showRoot = false
    massActionList = ['remove']
    selectable = false
    createDisabled = false
    selectedData = null
    level = 0
    itemViewName = 'views/record/list-tree-item'

    data() {
        let data = super.data();

        data.createDisabled = this.createDisabled;

        data.showRoot = this.showRoot;

        if (data.showRoot) {
            data.rootName = this.rootName || this.translate('Root');
        }

        data.showEditLink = this.showEditLink;

        if (this.level === 0 && this.selectable && (this.selectedData || {}).id === null) {
            data.rootIsSelected = true;
        }

        if (this.level === 0 && this.options.hasExpandedToggler) {
            data.hasExpandedToggler = true;
        }

        if (this.level === 0) {
            data.isExpanded = this.isExpanded;
        }

        if (data.hasExpandedToggler || this.showEditLink) {
            data.showRootMenu = true;
        }

        if (this.options.menuDisabled) {
            data.showRootMenu = false;
        }

        data.noData = data.createDisabled && !data.rowList.length && !data.showRoot;

        return data;
    }

    setup() {
        if ('selectable' in this.options) {
            this.selectable = this.options.selectable;
        }

        this.readOnly = this.options.readOnly;
        this.createDisabled = this.readOnly || this.options.createDisabled || this.createDisabled;
        this.isExpanded = this.options.isExpanded;

        if ('showRoot' in this.options) {
            this.showRoot = this.options.showRoot;

            if ('rootName' in this.options) {
                this.rootName = this.options.rootName;
            }
        }

        if ('showRoot' in this.options) {
            this.showEditLink = this.options.showEditLink;
        }

        if ('level' in this.options) {
            this.level = this.options.level;
        }

        this.rootView = this.options.rootView || this;

        if (this.level === 0) {
            this.selectedData = {
                id: null,
                path: [],
                names: {},
            };
        }

        if ('selectedData' in this.options) {
            this.selectedData = this.options.selectedData;
        }

        super.setup();

        if (this.selectable) {
            this.on('select', o => {
                if (o.id) {
                    this.$el.find('a.link[data-id="'+o.id+'"]').addClass('text-bold');

                    if (this.level === 0) {
                        this.$el.find('a.link').removeClass('text-bold');
                        this.$el.find('a.link[data-id="'+o.id+'"]').addClass('text-bold');

                        this.setSelected(o.id);

                        o.selectedData = this.selectedData;
                    }
                }

                if (this.level > 0) {
                    this.getParentView().trigger('select', o);
                }
            });
        }
    }

    /**
     * @param {string|null} id
     */
    setSelected(id) {
        if (id === null) {
            this.selectedData.id = null;
        }
        else {
            this.selectedData.id = id;
        }

        this.rowList.forEach(key => {
            let view = /** @type module:views/record/list-tree-item */this.getView(key);

            if (view.model.id === id) {
                view.setIsSelected();
            }
            else {
                view.isSelected = false;
            }

            if (view.hasView('children')) {
                view.getChildrenView().setSelected(id);
            }
        });
    }

    buildRows(callback) {
        this.checkedList = [];
        this.rowList = [];

        if (this.collection.length > 0) {
            this.wait(true);

            let modelList = this.collection.models;
            let count = modelList.length;
            let built = 0;

            modelList.forEach(model => {
                let key = model.id;

                this.rowList.push(key);

                this.createView(key, this.itemViewName, {
                    model: model,
                    collection: this.collection,
                    selector: this.getRowSelector(model.id),
                    createDisabled: this.createDisabled,
                    readOnly: this.readOnly,
                    level: this.level,
                    isSelected: model.id === this.selectedData.id,
                    selectedData: this.selectedData,
                    selectable: this.selectable,
                    setViewBeforeCallback: this.options.skipBuildRows && !this.isRendered(),
                    rootView: this.rootView,
                }, () => {
                    built++;

                    if (built === count) {
                        if (typeof callback === 'function') {
                            callback();
                        }

                        this.wait(false);
                    }
                });
            });

            return;
        }

        if (typeof callback === 'function') {
            callback();
        }
    }

    getRowSelector(id) {
        return 'li[data-id="' + id + '"]';
    }

    getItemEl(model, item) {
        return this.getSelector() +
            ' li[data-id="' + model.id + '"] span.cell[data-name="' + item.name + '"]';
    }

    getCreateAttributes() {
        return {};
    }

    // noinspection JSUnusedGlobalSymbols
    actionCreate(data, e) {
        e.stopPropagation();

        let attributes = this.getCreateAttributes();

        let maxOrder = 0;

        this.collection.models.forEach(m => {
            if (m.get('order') > maxOrder) {
                maxOrder = m.get('order');
            }
        });

        attributes.order = maxOrder + 1;

        attributes.parentId = null;
        attributes.parentName = null;

        if (this.model) {
            attributes.parentId = this.model.id;
            attributes.parentName = this.model.get('name');
        }

        let scope = this.collection.entityType;

        let viewName = this.getMetadata().get('clientDefs.' + scope + '.modalViews.edit') ||
            'views/modals/edit';

        this.createView('quickCreate', viewName, {
            scope: scope,
            attributes: attributes,
        }, view => {
            view.render();

            this.listenToOnce(view, 'after:save', model => {
                view.close();

                let collection = /** @type module:collections/tree */ this.collection;

                model.set('childCollection', collection.createSeed());

                if (model.get('parentId') !== attributes.parentId) {
                    let v = this;

                    while (1) {
                        if (v.level) {
                            v = v.getParentView().getParentView();
                        }
                        else {
                            break;
                        }
                    }

                    v.collection.fetch();

                    return;
                }

                this.collection.push(model);

                this.buildRows(() => {
                    this.render();
                });
            });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionSelectRoot() {
        this.trigger('select', {id: null});

        if (this.selectable) {
            this.$el.find('a.link').removeClass('text-bold');
            this.$el.find('a.link[data-action="selectRoot"]').addClass('text-bold');

            this.setSelected(null);
        }
    }
}

export default ListTreeRecordView;
PK]��#xc
c
views/record/panel-actions.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';

class PanelActionsView extends View {

    template = 'record/panel-actions'

    data() {
        return {
            defs: this.options.defs,
            buttonList: this.getButtonList(),
            actionList: this.getActionList(),
            entityType: this.options.entityType,
            scope: this.options.scope,
        };
    }

    setup() {
        this.buttonList = this.options.defs.buttonList || [];
        this.actionList = this.options.defs.actionList || [];
        this.defs = this.options.defs;
    }

    getButtonList() {
        let list = [];

        this.buttonList.forEach(item => {
            if (item.hidden) {
                return;
            }

            list.push(item);
        });

        return list;
    }

    getActionList() {
        return this.actionList
            .filter(item => !item.hidden)
            .map(item => {
                item = Espo.Utils.clone(item);

                if (item.action) {
                    item.data = Espo.Utils.clone(item.data || {});
                    item.data.panel = this.options.defs.name;
                }

                return item;
            });
    }
}

export default PanelActionsView;
PK]Ya�l		views/record/deleted-detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/record/deleted-detail', ['views/record/detail'], function (Dep) {

    return Dep.extend({

        bottomView: null,

        sideView: 'views/record/deleted-detail-side',

        setupBeforeFinal: function () {
            Dep.prototype.setupBeforeFinal.call(this);

            this.buttonList = [];
            this.dropdownItemList = [];

            this.addDropdownItem({
                name: 'restoreDeleted',
                label: 'Restore'
            });
        },

        actionRestoreDeleted: function () {
            Espo.Ui.notify(' ... ');

            Espo.Ajax.postRequest(this.model.entityType + '/action/restoreDeleted', {
                id: this.model.id
            }).then(() => {
                Espo.Ui.notify(false);

                this.model.set('deleted', false);
                this.model.trigger('after:restore-deleted');
            });
        },
    });
});
PK]��f�m�m views/record/panels-container.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/record/panels-container */

import View from 'view';

/**
 * A panel container view. For bottom and side views.
 */
class PanelsContainerRecordView extends View {

    /** @private */
    panelSoftLockedTypeList = ['default', 'acl', 'delimiter', 'dynamicLogic']

    /**
     * A panel.
     *
     * @typedef {Object} module:views/record/panels-container~panel
     *
     * @property {string} name A name.
     * @property {boolean} [hidden] Hidden.
     * @property {string} [label] A label.
     * @property {'default'|'success'|'danger'|'warning'} [style] A style.
     * @property {string} [titleHtml] A title HTML.
     * @property {boolean} [notRefreshable] Not refreshable.
     * @property {boolean} [isForm] If for a form.
     * @property {module:views/record/panels-container~button[]} [buttonList] Buttons.
     * @property {module:views/record/panels-container~action[]} [actionList] Dropdown actions.
     * @property {string} [view] A view name.
     * @property {Object.<string, *>} [Options] A view options.
     * @property {boolean} [sticked] To stick to an upper panel.
     * @property {Number} [tabNumber] A tab number.
     * @property {string} [aclScope] A scope to check access to.
     * @property {Espo.Utils~AccessDefs[]} [accessDataList] Access control defs.
     */

    /**
     * A button. Handled by an `action{Action}` method or a click handler.
     *
     * @typedef {Object} module:views/record/panels-container~button
     *
     * @property {string} action An action.
     * @property {boolean} [hidden] Hidden.
     * @property {string} [label] A label. Translatable.
     * @property {string} [html] A HTML.
     * @property {string} [text] A text.
     * @property {string} [title] A title (on hover). Translatable.
     * @property {Object.<string, (string|number|boolean)>} [data] Data attributes.
     * @property {function()} [onClick] A click event.
     */

    /**
     * An action. Handled by an `action{Action}` method or a click handler.
     *
     * @typedef {Object} module:views/record/panels-container~action
     *
     * @property {string} [action] An action.
     * @property {string} [link] A link URL.
     * @property {boolean} [hidden] Hidden.
     * @property {string} [label] A label. Translatable.
     * @property {string} [html] A HTML.
     * @property {string} [text] A text.
     * @property {Object.<string, (string|number|boolean)>} [data] Data attributes.
     * @property {function()} [onClick] A click event.
     */

    /**
     * A panel list.
     *
     * @protected
     * @type {module:views/record/panels-container~panel[]}
     */
    panelList = null

    /** @private */
    hasTabs = false

    /**
     * @private
     * @type {Object.<string,*>[]|null}
     */
    tabDataList = null

    /**
     * @protected
     */
    currentTab = 0

    /**
     * @protected
     * @type {string}
     */
    scope = ''

    /**
     * @protected
     * @type {string}
     */
    entityType =  ''

    /**
     * @protected
     * @type {string}
     */
    name =  ''

    /**
     * A mode.
     *
     * @type 'detail'|'edit'
     */
    mode = 'detail'

    data() {
        let tabDataList = this.hasTabs ? this.getTabDataList() : [];

        return {
            panelList: this.panelList,
            scope: this.scope,
            entityType: this.entityType,
            tabDataList: tabDataList,
        };
    }

    events = {
        'click .action': function (e) {
            let $target = $(e.currentTarget);
            let panel = $target.data('panel');

            if (!panel) {
                return;
            }

            let panelView = this.getView(panel);

            if (!panelView) {
                return;
            }

            let actionItems;

            if (
                typeof panelView.getButtonList === 'function' &&
                typeof panelView.getActionList === 'function'
            ) {
                actionItems = [...panelView.getButtonList(), ...panelView.getActionList()];
            }

            Espo.Utils.handleAction(panelView, e.originalEvent, e.currentTarget, {
                actionItems: actionItems,
                className: 'panel-action',
            });

            // @todo Check data. Maybe pass cloned data with unset params.

            /*
            let action = $target.data('action');
            let data = $target.data();

            if (action && panel) {
                let method = 'action' + Espo.Utils.upperCaseFirst(action);
                let d = _.clone(data);

                delete d['action'];
                delete d['panel'];

                let view = this.getView(panel);

                if (view && typeof view[method] == 'function') {
                    view[method].call(view, d, e);
                }
            }*/
        },
        'click .panels-show-more-delimiter [data-action="showMorePanels"]': 'actionShowMorePanels',
        /** @this module:views/record/panels-container */
        'click .tabs > button': function (e) {
            let tab = parseInt($(e.currentTarget).attr('data-tab'));

            this.selectTab(tab);
        },
    }

    afterRender() {
        this.adjustPanels();
    }

    adjustPanels() {
        if (!this.isRendered()) {
            return;
        }

        let $panels = this.$el.find('> .panel');

        $panels
            .removeClass('first')
            .removeClass('last')
            .removeClass('in-middle');

        let $visiblePanels = $panels.filter(`:not(.tab-hidden):not(.hidden)`);

        let groups = [];
        let currentGroup = [];
        let inTab = false;

        $visiblePanels.each((i, el) => {
            let $el = $(el);

            let breakGroup = false;

            if (
                !breakGroup &&
                this.hasTabs &&
                !inTab &&
                $el.attr('data-tab') !== '-1'
            ) {
                inTab = true;
                breakGroup = true;
            }

            if (!breakGroup && !$el.hasClass('sticked')) {
                breakGroup = true;
            }

            if (breakGroup) {
                if (i !== 0) {
                    groups.push(currentGroup);
                }

                currentGroup = [];
            }

            currentGroup.push($el);

            if (i === $visiblePanels.length - 1) {
                groups.push(currentGroup);
            }
        });

        groups.forEach(group => {
            group.forEach(($el, i) => {
                if (i === group.length - 1) {
                    if (i === 0) {
                        return;
                    }

                    $el.addClass('last')

                    return;
                }

                if (i === 0 && group.length) {
                    $el.addClass('first')

                    return;
                }

                $el.addClass('in-middle');
            });
        });
    }

    /**
     * Set read-only.
     */
    setReadOnly() {
        this.readOnly = true;
    }

    /**
     * Set not read-only.
     */
    setNotReadOnly(onlyNotSetAsReadOnly) {
        this.readOnly = false;

        if (onlyNotSetAsReadOnly) {
            this.panelList.forEach(item => {
                this.applyAccessToActions(item.buttonList);
                this.applyAccessToActions(item.actionList);

                if (this.isRendered()) {
                    let actionsView = this.getView(item.actionsViewKey);

                    if (actionsView) {
                        actionsView.reRender();
                    }
                }
            });
        }
    }

    /**
     * @private
     * @param {Object[]} actionList
     */
    applyAccessToActions(actionList) {
        if (!actionList) {
            return;
        }

        actionList.forEach(item => {
            if (!Espo.Utils.checkActionAvailability(this.getHelper(), item)) {
                item.hidden = true;

                return;
            }

            if (Espo.Utils.checkActionAccess(this.getAcl(), this.model, item, true)) {
                if (item.isHiddenByAcl) {
                    item.isHiddenByAcl = false;
                    item.hidden = false;
                }
            }
            else {
                if (!item.hidden) {
                    item.isHiddenByAcl = true;
                    item.hidden = true;
                }
            }
        });
    }

    /**
     * Set up panel views.
     *
     * @protected
     */
    setupPanelViews() {
        this.panelList.forEach(p => {
            let name = p.name;

            let options = {
                model: this.model,
                panelName: name,
                selector: '.panel[data-name="' + name + '"] > .panel-body',
                defs: p,
                mode: this.mode,
                recordHelper: this.recordHelper,
                inlineEditDisabled: this.inlineEditDisabled,
                readOnly: this.readOnly,
                disabled: p.hidden || false,
                recordViewObject: this.recordViewObject,
                dataObject: this.options.dataObject,
            };

            options = _.extend(options, p.options);

            this.createView(name, p.view, options, (view) => {
                if ('getActionList' in view) {
                    p.actionList = view.getActionList();

                    this.applyAccessToActions(p.actionList);
                }

                if ('getButtonList' in view) {
                    p.buttonList = view.getButtonList();
                    this.applyAccessToActions(p.buttonList);
                }

                if (view.titleHtml) {
                    p.titleHtml = view.titleHtml;
                }
                else {
                    if (p.label) {
                        p.title = this.translate(p.label, 'labels', this.scope);
                    }
                    else {
                        p.title = view.title;
                    }
                }

                this.createView(name + 'Actions', 'views/record/panel-actions', {
                    selector: '.panel[data-name="' + p.name + '"] > .panel-heading > .panel-actions-container',
                    model: this.model,
                    defs: p,
                    scope: this.scope,
                    entityType: this.entityType,
                });
            });
        });
    }

    /**
     * Set up panels.
     *
     * @protected
     */
    setupPanels() {}

    /**
     * Get field views.
     *
     * @param {boolean} [withHidden] With hidden.
     * @return {Object.<string, module:views/fields/base>}
     */
    getFieldViews(withHidden) {
        let fields = {};

        this.panelList.forEach(p => {
            let panelView = this.getView(p.name);

            if ((!panelView.disabled || withHidden) && 'getFieldViews' in panelView) {
                fields = _.extend(fields, panelView.getFieldViews());
            }
        });

        return fields;
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * @deprecated Use `getFieldViews`.
     * @todo Remove in v9.0.
     */
    getFields() {
        return this.getFieldViews();
    }

    /**
     * Fetch.
     *
     * @return {Object.<string, *>}
     */
    fetch() {
        let data = {};

        this.panelList.forEach(p => {
            let panelView = this.getView(p.name);

            if (!panelView.disabled && 'fetch' in panelView) {
                data = _.extend(data, panelView.fetch());
            }
        });

        return data;
    }

    /**
     * @param {string} name
     * @return {boolean}
     */
    hasPanel(name) {
        return !!this.panelList.find(item => item.name === name);
    }

    processShowPanel(name, callback, wasShown) {
        if (this.recordHelper.getPanelStateParam(name, 'hidden')) {
            return;
        }

        if (!this.hasPanel(name)) {
            return;
        }

        this.panelList.filter(item => item.name === name).forEach(item => {
            item.hidden = false;

            if (typeof item.tabNumber !== 'undefined') {
                this.controlTabVisibilityShow(item.tabNumber);
            }
        });

        this.showPanelFinalize(name, callback, wasShown);
    }

    processHidePanel(name, callback) {
        if (!this.recordHelper.getPanelStateParam(name, 'hidden')) {
            return;
        }

        if (!this.hasPanel(name)) {
            return;
        }

         this.panelList.filter(item => item.name === name).forEach(item => {
            item.hidden = true;

            if (typeof item.tabNumber !== 'undefined') {
                this.controlTabVisibilityHide(item.tabNumber);
            }
        });

        this.hidePanelFinalize(name, callback);
    }

    showPanelFinalize(name, callback, wasShown) {
        let process = (wasRendered) => {
            let view = this.getView(name);

            if (view) {
                view.$el.closest('.panel').removeClass('hidden');

                view.disabled = false;
                view.trigger('show');
                view.trigger('panel-show-propagated');

                if (wasRendered && !wasShown && view.getFieldViews) {
                    let fields = view.getFieldViews();

                    if (fields) {
                        for (let i in fields) {
                            fields[i].reRender();
                        }
                    }
                }
            }

            if (typeof callback === 'function') {
                callback.call(this);
            }
        };

        if (this.isRendered()) {
            process(true);

            this.adjustPanels();

            return;
        }

        this.once('after:render', () => {
            process();
        });
    }

    hidePanelFinalize(name, callback) {
        if (this.isRendered()) {
            let view = this.getView(name);

            if (view) {
                view.$el.closest('.panel').addClass('hidden');
                view.disabled = true;
                view.trigger('hide');
            }

            if (typeof callback === 'function') {
                callback.call(this);
            }

            this.adjustPanels();

            return;
        }

        if (typeof callback === 'function') {
            this.once('after:render', () => {
                callback.call(this);
            });
        }
    }

    showPanel(name, softLockedType, callback) {
        if (!this.hasPanel(name)) {
            return;
        }

        if (this.recordHelper.getPanelStateParam(name, 'hiddenLocked')) {
            return;
        }

        if (softLockedType) {
            let param = 'hidden' + Espo.Utils.upperCaseFirst(softLockedType) + 'Locked';

            this.recordHelper.setPanelStateParam(name, param, false);

            for (let i = 0; i < this.panelSoftLockedTypeList.length; i++) {
                let iType = this.panelSoftLockedTypeList[i];

                if (iType === softLockedType) {
                    continue;
                }

                let iParam = 'hidden' +  Espo.Utils.upperCaseFirst(iType) + 'Locked';

                if (this.recordHelper.getPanelStateParam(name, iParam)) {
                    return;
                }
            }
        }

        let wasShown = this.recordHelper.getPanelStateParam(name, 'hidden') === false;

        this.recordHelper.setPanelStateParam(name, 'hidden', false);

        this.processShowPanel(name, callback, wasShown);
    }

    hidePanel(name, locked, softLockedType, callback) {
        if (!this.hasPanel(name)) {
            return;
        }

        this.recordHelper.setPanelStateParam(name, 'hidden', true);

        if (locked) {
            this.recordHelper.setPanelStateParam(name, 'hiddenLocked', true);
        }

        if (softLockedType) {
            let param = 'hidden' + Espo.Utils.upperCaseFirst(softLockedType) + 'Locked';

            this.recordHelper.setPanelStateParam(name, param, true);
        }

        this.processHidePanel(name, callback);
    }

    alterPanels(layoutData) {
        layoutData = layoutData || this.layoutData || {};

        let tabBreakIndexList = [];

        let tabDataList = [];

        for (let name in layoutData) {
            let item = layoutData[name];

            if (name === '_delimiter_') {
                this.panelList.push({
                    name: name,
                });
            }

            if (item.tabBreak) {
                tabBreakIndexList.push(item.index);

                tabDataList.push({
                    index: item.index,
                    label: item.tabLabel,
                })
            }
        }

        /**
         * @private
         * @type {Object.<string,*>[]}
         */
        this.tabDataList = tabDataList.sort((v1, v2) => v1.index - v2.index);

        let newList = [];

        this.panelList.forEach((item, i) => {
            item.index = ('index' in item) ? item.index : i;

            let allowedInLayout = false;

            if (item.name) {
                let itemData = layoutData[item.name] || {};

                if (itemData.disabled) {
                    return;
                }

                if (layoutData[item.name]) {
                    allowedInLayout = true;
                }

                for (let i in itemData) {
                    item[i] = itemData[i];
                }
            }

            if (item.disabled && !allowedInLayout) {
                return;
            }

            item.tabNumber = tabBreakIndexList.length -
                tabBreakIndexList.slice().reverse().findIndex(index => item.index > index) - 1;

            if (item.tabNumber === tabBreakIndexList.length) {
                item.tabNumber = -1;
            }

            newList.push(item);
        });

        newList.sort((v1, v2) => v1.index - v2.index);

        let firstTabIndex = newList.findIndex(item => item.tabNumber !== -1);

        if (firstTabIndex !== -1) {
            newList[firstTabIndex].isTabsBeginning = true;
            this.hasTabs = true;
            this.currentTab = newList[firstTabIndex].tabNumber;

            this.panelList
                .filter(item => item.tabNumber !== -1 && item.tabNumber !== this.currentTab)
                .forEach(item => {
                    item.tabHidden = true;
                });

            this.panelList
                .forEach((item, i) => {
                    if (
                        item.tabNumber !== -1 &&
                        (i === 0 || this.panelList[i - 1].tabNumber !== item.tabNumber)
                    ) {
                        item.sticked = false;
                    }
                });
        }

        this.panelList = newList;

        if (this.recordViewObject && this.recordViewObject.dynamicLogic) {
            let dynamicLogic = this.recordViewObject.dynamicLogic;

            this.panelList.forEach(item => {
                if (item.dynamicLogicVisible) {
                    dynamicLogic.addPanelVisibleCondition(item.name, item.dynamicLogicVisible);

                    if (this.recordHelper.getPanelStateParam(item.name, 'hidden')) {
                        item.hidden = true;
                    }
                }

                if (item.style && item.style !== 'default' && item.dynamicLogicStyled) {
                    dynamicLogic.addPanelStyledCondition(item.name, item.dynamicLogicStyled);
                }
            });
        }

        if (
            this.hasTabs &&
            this.options.isReturn &&
            this.isStoredTabForThisRecord()
        ) {
            this.selectStoredTab();
        }
    }

    setupPanelsFinal() {
        let afterDelimiter = false;
        let rightAfterDelimiter = false;

        let index = -1;

        this.panelList.forEach((p, i) => {
            if (p.name === '_delimiter_') {
                afterDelimiter = true;
                rightAfterDelimiter = true;
                index = i;

                return;
            }

            if (afterDelimiter) {
                p.hidden = true;
                p.hiddenAfterDelimiter = true;

                this.recordHelper.setPanelStateParam(p.name, 'hidden', true);
                this.recordHelper.setPanelStateParam(p.name, 'hiddenDelimiterLocked', true);
            }

            if (rightAfterDelimiter) {
                p.isRightAfterDelimiter = true;
                rightAfterDelimiter = false;
            }
        });

        if (~index) {
            this.panelList.splice(index, 1);
        }

        this.panelList = this.panelList.filter((p) => {
            return !this.recordHelper.getPanelStateParam(p.name, 'hiddenLocked');
        });

        this.panelsAreSet = true;

        this.trigger('panels-set');
    }

    actionShowMorePanels() {
        this.panelList.forEach(p => {
            if (!p.hiddenAfterDelimiter) {
                return;
            }

            delete p.isRightAfterDelimiter;

            this.showPanel(p.name, 'delimiter');
        });

        this.$el.find('.panels-show-more-delimiter').remove();
    }

    onPanelsReady(callback) {
        Promise.race([
            new Promise(resolve => {
                if (this.panelsAreSet) {
                    resolve();
                }
            }),
            new Promise(resolve => {
                this.once('panels-set', resolve);
            })
        ]).then(() => {
            callback.call(this);
        });
    }

    getTabDataList() {
        return this.tabDataList.map((item, i) => {
            let label = item.label;

            if (!label) {
                label = (i + 1).toString();
            }
            else if (label[0] === '$') {
                label = this.translate(label.substring(1), 'tabs', this.scope);
            }

            let hidden = this.panelList
                .filter(panel => panel.tabNumber === i)
                .findIndex(panel => !this.recordHelper.getPanelStateParam(panel.name, 'hidden')) === -1;

            return {
                label: label,
                isActive: i === this.currentTab,
                hidden: hidden,
            };
        });
    }

    selectTab(tab) {
        this.currentTab = tab;

        if (this.isRendered()) {
            $('body > .popover').remove();

            this.$el.find('.tabs > button').removeClass('active');
            this.$el.find(`.tabs > button[data-tab="${tab}"]`).addClass('active');

            this.$el.find('.panel[data-tab]:not([data-tab="-1"])').addClass('tab-hidden');
            this.$el.find(`.panel[data-tab="${tab}"]`).removeClass('tab-hidden');
        }

        this.adjustPanels();

        this.panelList
            .filter(item => item.tabNumber === tab && item.name)
            .forEach(item => {
                let view = this.getView(item.name);

                if (view) {
                    view.trigger('tab-show');

                    view.propagateEvent('panel-show-propagated');
                }

                item.tabHidden = false;
            });

        this.panelList
            .filter(item => item.tabNumber !== tab && item.name)
            .forEach(item => {
                let view = this.getView(item.name);

                if (view) {
                    view.trigger('tab-hide');
                }

                if (item.tabNumber > -1) {
                    item.tabHidden = true;
                }
            });

        this.storeTab();
    }

    /** @private */
    storeTab() {
        let key = 'tab_' + this.name;
        let keyRecord = 'tab_' + this.name + '_record';

        this.getSessionStorage().set(key, this.currentTab);
        this.getSessionStorage().set(keyRecord, this.entityType + '_' + this.model.id);
    }

    /** @private */
    isStoredTabForThisRecord() {
        let keyRecord = 'tab_' + this.name + '_record';

        return this.getSessionStorage().get(keyRecord) === this.entityType + '_' + this.model.id;
    }

    /** @private */
    selectStoredTab() {
        let key = 'tab_' + this.name;

        let tab = this.getSessionStorage().get(key);

        if (tab > 0) {
            this.selectTab(tab);
        }
    }

    /** @private */
    controlTabVisibilityShow(tab) {
        if (!this.hasTabs) {
            return;
        }

        if (this.isBeingRendered()) {
            this.once('after:render', () => this.controlTabVisibilityShow(tab));

            return;
        }

        this.$el.find(`.tabs > [data-tab="${tab.toString()}"]`).removeClass('hidden');
    }

    /** @private */
    controlTabVisibilityHide(tab) {
        if (!this.hasTabs) {
            return;
        }

        if (this.isBeingRendered()) {
            this.once('after:render', () => this.controlTabVisibilityHide(tab));

            return;
        }

        let panelList = this.panelList.filter(panel => panel.tabNumber === tab);

        let allIsHidden = panelList
            .findIndex(panel => !this.recordHelper.getPanelStateParam(panel.name, 'hidden')) === -1;

        if (!allIsHidden) {
            return;
        }

        let $tab = this.$el.find(`.tabs > [data-tab="${tab.toString()}"]`);

        $tab.addClass('hidden');

        if (this.currentTab === tab) {
            let firstVisiblePanel = this.panelList
                .find(panel => panel.tabNumber > -1 && !panel.hidden);

            let firstVisibleTab = firstVisiblePanel ?
                firstVisiblePanel.tabNumber : 0;

            this.selectTab(firstVisibleTab);
        }
    }
}

export default PanelsContainerRecordView;
PK]}��3�T�T#views/record/panels/relationship.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/record/panels/relationship */

import BottomPanelView from 'views/record/panels/bottom';
import SearchManager from 'search-manager';
import RecordModal from 'helpers/record-modal';

/**
 * A relationship panel.
 */
class RelationshipPanelView extends BottomPanelView {

    /** @inheritDoc */
    template = 'record/panels/relationship'

    /**
     * A row-actions view.
     *
     * @protected
     */
    rowActionsView = 'views/record/row-actions/relationship'

    /**
     * An API URL.
     *
     * @protected
     * @type {string|null}
     */
    url = null

    /**
     * A scope.
     *
     * @type {string|null}
     */
    scope = null

    /**
     * Read-only.
     */
    readOnly = false

    /**
     * Fetch a collection on a model 'after:relate' event.
     *
     * @protected
     */
    fetchOnModelAfterRelate = false

    /**
     * @protected
     */
    noCreateScopeList = ['User', 'Team', 'Role', 'Portal']

    /**
     * @private
     */
    recordsPerPage = null

    /**
     * @protected
     */
    viewModalView = null

    setup() {
        super.setup();

        this.link = this.link || this.defs.link || this.panelName;

        if (!this.scope && !(this.link in this.model.defs.links)) {
            throw new Error(`Link '${this.link}' is not defined in model '${this.model.entityType}'`);
        }

        this.scope = this.scope || this.model.defs.links[this.link].entity;

        const linkReadOnly = this.getMetadata()
            .get(['entityDefs', this.model.entityType, 'links', this.link, 'readOnly']) || false;

        const url = this.url = this.url || this.model.entityType + '/' + this.model.id + '/' + this.link;

        if (!('create' in this.defs)) {
            this.defs.create = true;
        }

        if (!('select' in this.defs)) {
            this.defs.select = true;
        }

        if (!('view' in this.defs)) {
            this.defs.view = true;
        }

        if (linkReadOnly) {
            this.defs.create = false;
            this.defs.select = false;
        }

        this.filterList = this.defs.filterList || this.filterList || null;

        if (this.filterList && this.filterList.length) {
            this.filter = this.getStoredFilter() || this.filterList[0];

            if (this.filter === 'all') {
                this.filter = null;
            }
        }

        this.setupTitle();

        if (this.defs.createDisabled) {
            this.defs.create = false;
        }

        if (this.defs.selectDisabled) {
            this.defs.select = false;
        }

        if (this.defs.viewDisabled) {
            this.defs.view = false;
        }

        let hasCreate = false;

        if (this.defs.create) {
            if (
                this.getAcl().check(this.scope, 'create') &&
                !~this.noCreateScopeList.indexOf(this.scope)
            ) {
                this.buttonList.push({
                    title: 'Create',
                    action: this.defs.createAction || 'createRelated',
                    link: this.link,
                    html: '<span class="fas fa-plus"></span>',
                    data: {
                        link: this.link,
                    },
                    acl: this.defs.createRequiredAccess || null,
                });

                hasCreate = true;
            }
        }

        if (this.defs.select) {
            const data = {link: this.link};

            if (this.defs.selectPrimaryFilterName) {
                data.primaryFilterName = this.defs.selectPrimaryFilterName;
            }

            if (this.defs.selectBoolFilterList) {
                data.boolFilterList = this.defs.selectBoolFilterList;
            }

            data.massSelect = this.defs.massSelect;
            data.createButton = hasCreate;

            this.actionList.unshift({
                label: 'Select',
                action: this.defs.selectAction || 'selectRelated',
                data: data,
                acl: this.defs.selectRequiredAccess || 'edit',
            });
        }

        if (this.defs.view) {
            this.actionList.unshift({
                label: 'View List',
                action: this.defs.viewAction || 'viewRelatedList',
            });
        }

        this.setupActions();

        let layoutName = 'listSmall';

        this.setupListLayout();

        if (this.listLayoutName) {
            layoutName = this.listLayoutName;
        }

        let listLayout = null;

        const layout = this.defs.layout || null;

        if (layout) {
            if (typeof layout === 'string') {
                 layoutName = layout;
            } else {
                 layoutName = 'listRelationshipCustom';
                 listLayout = layout;
            }
        }

        this.listLayout = listLayout;
        this.layoutName = layoutName;

        this.setupSorting();

        this.wait(true);

        this.getCollectionFactory().create(this.scope, collection => {
            collection.maxSize = this.recordsPerPage || this.getConfig().get('recordsPerPageSmall') || 5;

            if (this.defs.filters) {
                const searchManager = new SearchManager(collection, 'listRelationship', null, this.getDateTime());

                searchManager.setAdvanced(this.defs.filters);
                collection.where = searchManager.getWhere();
            }

            collection.url = collection.urlRoot = url;

            if (this.defaultOrderBy) {
                collection.setOrder(this.defaultOrderBy, this.defaultOrder || false, true);
            }

            this.collection = collection;

            collection.parentModel = this.model;

            this.setFilter(this.filter);

            if (this.fetchOnModelAfterRelate) {
                this.listenTo(this.model, 'after:relate', () => collection.fetch());
            }

            this.listenTo(this.model, 'update-all', () => collection.fetch());

            const viewName =
                this.defs.recordListView ||
                this.getMetadata().get(['clientDefs', this.scope, 'recordViews', 'listRelated']) ||
                this.getMetadata().get(['clientDefs', this.scope, 'recordViews', 'list']) ||
                'views/record/list';

            this.listViewName = viewName;
            this.rowActionsView = this.defs.readOnly ? false : (this.defs.rowActionsView || this.rowActionsView);

            this.once('after:render', () => {
                this.createView('list', viewName, {
                    collection: collection,
                    layoutName: layoutName,
                    listLayout: listLayout,
                    checkboxes: false,
                    rowActionsView: this.rowActionsView,
                    buttonsDisabled: true,
                    selector: '.list-container',
                    skipBuildRows: true,
                    rowActionsOptions: {
                        unlinkDisabled: this.defs.unlinkDisabled,
                    },
                    displayTotalCount: false,
                }, view => {
                    view.getSelectAttributeList((selectAttributeList) => {
                        if (selectAttributeList) {
                            collection.data.select = selectAttributeList.join(',');
                        }

                        if (!this.defs.hidden) {
                            collection.fetch();

                            return;
                        }

                        this.once('show', () => collection.fetch());
                    });
                });
            });

            this.wait(false);
        });

        this.setupFilterActions();
        this.setupLast();
    }

    /**
     * Set up lastly.
     *
     * @protected
     */
    setupLast() {}

    /**
     * Set up title.
     *
     * @protected
     */
    setupTitle() {
        this.title = this.title || this.translate(this.link, 'links', this.model.entityType);

        let iconHtml = '';

        if (!this.getConfig().get('scopeColorsDisabled')) {
            iconHtml = this.getHelper().getScopeColorIconHtml(this.scope);
        }

        this.titleHtml = this.title;

        if (this.defs.label) {
            this.titleHtml = iconHtml + this.translate(this.defs.label, 'labels', this.scope);
        } else {
            this.titleHtml = iconHtml + this.title;
        }

        if (this.filter && this.filter !== 'all') {
            this.titleHtml += ' &middot; ' + this.translateFilter(this.filter);
        }
    }

    /**
     * Set up sorting.
     *
     * @protected
     */
    setupSorting() {
        let orderBy = this.defs.orderBy || this.defs.sortBy || this.orderBy;
        let order = this.defs.orderDirection || this.orderDirection || this.order;

        if ('asc' in this.defs) { // @todo Remove.
            order = this.defs.asc ? 'asc' : 'desc';
        }

        if (!orderBy) {
            orderBy = this.getMetadata().get(['entityDefs', this.scope, 'collection', 'orderBy']);
            order = this.getMetadata().get(['entityDefs', this.scope, 'collection', 'order'])
        }

        if (orderBy && !order) {
            order = 'asc';
        }

        this.defaultOrderBy = orderBy;
        this.defaultOrder = order;
    }

    /**
     * Set up a list layout.
     *
     * @protected
     */
    setupListLayout() {}

    /**
     * Set up actions.
     *
     * @protected
     */
    setupActions() {}

    /**
     * Set up filter actions.
     *
     * @protected
     */
    setupFilterActions() {
        if (!(this.filterList && this.filterList.length)) {
            return;
        }

        this.actionList.push(false);

        this.filterList.slice(0).forEach((item) => {
            let selected;

            selected = item === 'all' ?
                !this.filter :
                item === this.filter;

            const label = this.translateFilter(item);

            const $item =
                $('<div>')
                    .append(
                        $('<span>')
                            .addClass('check-icon fas fa-check pull-right')
                            .addClass(!selected ? 'hidden' : '')
                    )
                    .append(
                        $('<div>').text(label)
                    );

            this.actionList.push({
                action: 'selectFilter',
                html: $item.get(0).innerHTML,
                data: {
                    name: item,
                },
            });
        });
    }

    /**
     * Translate a filter.
     *
     * @param {string} name A name.
     * @return {string}
     */
    translateFilter(name) {
        return this.translate(name, 'presetFilters', this.scope);
    }

    /**
     * @protected
     */
    getStoredFilter() {
        const key = 'panelFilter' + this.model.entityType + '-' + (this.panelName || this.name);

        return this.getStorage().get('state', key) || null;
    }

    /**
     * @private
     */
    storeFilter(filter) {
        const key = 'panelFilter' + this.model.entityType + '-' + (this.panelName || this.name);

        if (filter) {
            this.getStorage().set('state', key, filter);
        } else {
            this.getStorage().clear('state', key);
        }
    }

    /**
     * Set a filter.
     *
     * @param {string} filter A filter.
     */
    setFilter(filter) {
        this.filter = filter;
        this.collection.data.primaryFilter = null;

        if (filter && filter !== 'all') {
            this.collection.data.primaryFilter = filter;
        }
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * A `select-filter` action.
     *
     * @protected
     */
    actionSelectFilter(data) {
        const filter = data.name;
        let filterInternal = filter;

        if (filter === 'all') {
            filterInternal = false;
        }

        this.storeFilter(filterInternal);
        this.setFilter(filterInternal);

        this.filterList.forEach(item => {
            const $el = this.$el.closest('.panel').find('[data-name="' + item + '"] span');

            if (item === filter) {
                $el.removeClass('hidden');
            } else {
                $el.addClass('hidden');
            }
        });

        this.collection.reset();

        const listView = this.getView('list');

        if (listView && listView.$el) {
            const height = listView.$el.parent().get(0).clientHeight;

            listView.$el.empty();

            if (height) {
                listView.$el.parent().css('height', height + 'px');
            }
        }

        this.collection.fetch().then(() => {
            listView.$el.parent().css('height', '');
        });

        this.setupTitle();

        if (this.isRendered()) {
            this.$el.closest('.panel')
                .find('> .panel-heading > .panel-title > span')
                .html(this.titleHtml);
        }
    }

    /**
     * A `refresh` action.
     *
     * @protected
     */
    actionRefresh() {
        this.collection.fetch();
    }

    /**
     * A `view-related-list` action.
     *
     * @protected
     */
    actionViewRelatedList(data) {
        const viewName =
            this.getMetadata().get(
                ['clientDefs', this.model.entityType, 'relationshipPanels', this.name, 'viewModalView']
            ) ||
            this.getMetadata().get(['clientDefs', this.scope, 'modalViews', 'relatedList']) ||
            this.viewModalView ||
            'views/modals/related-list';

        const scope = data.scope || this.scope;

        let filter = this.filter;

        if (this.relatedListFiltersDisabled) {
            filter = null;
        }

        const options = {
            model: this.model,
            panelName: this.panelName,
            link: this.link,
            scope: scope,
            defs: this.defs,
            title: data.title || this.title,
            filterList: this.filterList,
            filter: filter,
            layoutName: this.layoutName,
            defaultOrder: this.defaultOrder,
            defaultOrderBy: this.defaultOrderBy,
            url: data.url || this.url,
            listViewName: this.listViewName,
            createDisabled: !this.isCreateAvailable(scope),
            selectDisabled: !this.isSelectAvailable(scope),
            rowActionsView: this.rowActionsView,
            panelCollection: this.collection,
            filtersDisabled: this.relatedListFiltersDisabled,
        };

        if (data.viewOptions) {
            for (const item in data.viewOptions) {
                options[item] = data.viewOptions[item];
            }
        }

        Espo.Ui.notify(' ... ');

        this.createView('modalRelatedList', viewName, options, view => {
            Espo.Ui.notify(false);

            view.render();

            this.listenTo(view, 'action', (event, element) => {
                Espo.Utils.handleAction(this, event, element);
            });

            this.listenToOnce(view, 'close', () => {
                this.clearView('modalRelatedList');
            });
        });
    }

    /**
     * Is create available.
     *
     * @protected
     * @param {string} scope A scope (entity type).
     * @return {boolean};
     */
    isCreateAvailable(scope) {
        return !!this.defs.create;
    }

    // noinspection JSUnusedLocalSymbols
    /**
     * Is select available.
     *
     * @protected
     * @param {string} scope A scope (entity type).
     * @return {boolean};
     */
    isSelectAvailable(scope) {
        return !!this.defs.select;
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * A `view-related` action.
     *
     * @protected
     */
    actionViewRelated(data) {
        const id = data.id;
        const model = this.collection.get(id);

        if (!model) {
            return;
        }

        const scope = model.entityType;

        const helper = new RecordModal(this.getMetadata(), this.getAcl());

        helper
            .showDetail(this, {
                scope: scope,
                id: id,
                model: model,
            })
            .then(view => {
                this.listenTo(view, 'after:save', () => {
                    this.collection.fetch();
                });
            });
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * An `edit-related` action.
     *
     * @protected
     */
    actionEditRelated(data) {
        const id = data.id;
        const scope = this.collection.get(id).name;

        const viewName = this.getMetadata().get('clientDefs.' + scope + '.modalViews.edit') ||
            'views/modals/edit';

        Espo.Ui.notify(' ... ');

        this.createView('quickEdit', viewName, {
            scope: scope,
            id: id,
        }, (view) => {
            view.once('after:render', () => {
                Espo.Ui.notify(false);
            });

            view.render();

            view.once('after:save', () => {
                this.collection.fetch();
            });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * An `unlink-related` action.
     *
     * @protected
     */
    actionUnlinkRelated(data) {
        const id = data.id;

        this.confirm({
            message: this.translate('unlinkRecordConfirmation', 'messages'),
            confirmText: this.translate('Unlink'),
        }, () => {
            Espo.Ui.notify(' ... ');

            Espo.Ajax
                .deleteRequest(this.collection.url, {id: id})
                .then(() => {
                    Espo.Ui.success(this.translate('Unlinked'));

                    this.collection.fetch();

                    this.model.trigger('after:unrelate');
                    this.model.trigger('after:unrelate:' + this.link);
                });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * A `remove-related` action.
     *
     * @protected
     */
    actionRemoveRelated(data) {
        const id = data.id;

        this.confirm({
            message: this.translate('removeRecordConfirmation', 'messages'),
            confirmText: this.translate('Remove'),
        }, () => {
            const model = this.collection.get(id);

            Espo.Ui.notify(' ... ');

            model
                .destroy()
                .then(() => {
                    Espo.Ui.success(this.translate('Removed'));

                    this.collection.fetch();

                    this.model.trigger('after:unrelate');
                    this.model.trigger('after:unrelate:' + this.link);
                });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * An `unlink-all-related` action.
     *
     * @protected
     */
    actionUnlinkAllRelated(data) {
        this.confirm(this.translate('unlinkAllConfirmation', 'messages'), () => {
            Espo.Ui.notify(' ... ');

            Espo.Ajax
                .postRequest(this.model.entityType + '/action/unlinkAll', {
                    link: data.link,
                    id: this.model.id,
                })
                .then(() => {
                    Espo.Ui.success(this.translate('Unlinked'));

                    this.collection.fetch();

                    this.model.trigger('after:unrelate');
                    this.model.trigger('after:unrelate:' + this.link);
                });
        });
    }
}

export default RelationshipPanelView;
PK]�
UQ�'�'views/record/panels/bottom.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/record/panels/bottom */

import View from 'view';

/**
 * A bottom panel.
 */
class BottomPanelView extends View {

    template = 'record/panels/side'

    /**
     * A field list.
     *
     * @protected
     * @type {module:views/record/panels/side~field[]}
     */
    fieldList = null

    /**
     * @protected
     * @type {Array<module:views/record/panels-container~action|false>}
     */
    actionList = null

    /**
     * @protected
     * @type {module:views/record/panels-container~button[]}
     */
    buttonList = null

    defs = null

    /**
     * A mode.
     *
     * @protected
     * @type {'list'|'detail'|'edit'}
     */
    mode = 'detail'

    /**
     * Disable.
     *
     * @protected
     */
    disabled = false

    events = {
        /** @this BottomPanelView */
        'click .action': function (e) {
            Espo.Utils.handleAction(this, e.originalEvent, e.currentTarget, {
                actionItems: [...this.buttonList, ...this.actionList],
                className: 'panel-action',
            });
        },
    }

    data() {
        return {
            scope: this.scope,
            name: this.panelName,
            hiddenFields: this.recordHelper.getHiddenFields(),
            fieldList: this.getFieldList(),
        };
    }

    init() {
        this.panelName = this.options.panelName;
        this.defs = this.options.defs || {};
        this.recordHelper = this.options.recordHelper;

        if ('disabled' in this.options) {
            this.disabled = this.options.disabled;
        }

        this.mode = this.options.mode || this.mode;

        this.readOnlyLocked = this.options.readOnlyLocked || this.readOnly;
        this.readOnly = this.readOnly || this.options.readOnly;
        this.inlineEditDisabled = this.inlineEditDisabled || this.options.inlineEditDisabled;

        this.buttonList = Espo.Utils.cloneDeep(this.defs.buttonList || this.buttonList || []);
        this.actionList = Espo.Utils.cloneDeep(this.defs.actionList || this.actionList || []);

        this.fieldList = this.options.fieldList || this.fieldList || [];

        this.recordViewObject = this.options.recordViewObject;
    }

    setup() {
        this.setupFields();

        this.fieldList = this.fieldList.map((d) => {
            let item = d;

            if (typeof item !== 'object') {
                item = {
                    name: item,
                    viewKey: item + 'Field',
                };
            }

            item = Espo.Utils.clone(item);
            item.viewKey = item.name + 'Field';
            item.label = item.label || item.name;

            if (this.recordHelper.getFieldStateParam(item.name, 'hidden') !== null) {
                item.hidden = this.recordHelper.getFieldStateParam(item.name, 'hidden');
            }
            else {
                this.recordHelper.setFieldStateParam(item.name, item.hidden || false);
            }

            return item;
        });

        this.fieldList = this.fieldList.filter((item) => {
            if (!item.name) {
                return;
            }

            if (!(item.name in (((this.model.defs || {}).fields) || {}))) {
                return;
            }

            return true;
        });

        this.createFields();
    }

    /**
     * Set up fields.
     *
     * @protected
     */
    setupFields() {}

    /**
     * @return {module:views/record/panels-container~button[]}
     */
    getButtonList() {
        return this.buttonList || [];
    }

    /**
     * @return {module:views/record/panels-container~action[]}
     */
    getActionList() {
        return this.actionList || [];
    }

    /**
     * Get field views.
     *
     * @return {Object.<string,module:views/fields/base>}
     */
    getFieldViews() {
        let fields = {};

        this.getFieldList().forEach((item) => {
            if (this.hasView(item.viewKey)) {
                fields[item.name] = this.getView(item.viewKey);
            }
        });

        return fields;
    }

    /**
     * @deprecated Use `getFieldViews`.
     */
    getFields() {
        return this.getFieldViews();
    }

    /**
     * Get a field list.
     *
     * @return {module:views/record/panels/side~field[]}
     */
    getFieldList() {
        return this.fieldList.map(item => {
            if (typeof item !== 'object') {
                return {
                    name: item
                };
            }

            return item;
        });
    }

    /**
     * @private
     */
    createFields() {
        this.getFieldList().forEach(item => {
            let view = null;
            let field;
            let readOnly = null;

            if (typeof item === 'object') {
                field = item.name;
                view = item.view;

                if ('readOnly' in item) {
                    readOnly = item.readOnly;
                }
            }
            else {
               field = item;
            }

            if (!(field in this.model.defs.fields)) {
                return;
            }

            this.createField(field, view, null, null, readOnly);
        });
    }

    /**
     * Create a field view.
     *
     * @protected
     * @param {string} field A field name.
     * @param {string|null} [viewName] A view name/path.
     * @param {Object<string,*>} [params] Field params.
     * @param {'detail'|'edit'|'list'|null} [mode='edit'] A mode.
     * @param {boolean} [readOnly] Read-only.
     * @param {Object<string,*>} [options] View options.
     */
    createField(field, viewName, params, mode, readOnly, options) {
        const type = this.model.getFieldType(field) || 'base';

        viewName = viewName ||
            this.model.getFieldParam(field, 'view') ||
            this.getFieldManager().getViewName(type);

        const o = {
            model: this.model,
            selector: '.field[data-name="' + field + '"]',
            defs: {
                name: field,
                params: params || {},
            },
            mode: mode || this.mode,
            dataObject: this.options.dataObject,
        };

        if (options) {
            for (let param in options) {
                o[param] = options[param];
            }
        }

        let readOnlyLocked = this.readOnlyLocked;

        if (this.readOnly) {
            o.readOnly = true;
        }
        else {
            if (readOnly !== null) {
                o.readOnly = readOnly;
            }
        }

        if (readOnly) {
            readOnlyLocked = true;
        }

        if (this.inlineEditDisabled) {
            o.inlineEditDisabled = true;
        }

        if (this.recordHelper.getFieldStateParam(field, 'hidden')) {
            o.disabled = true;
        }
        if (this.recordHelper.getFieldStateParam(field, 'hiddenLocked')) {
            o.disabledLocked = true;
        }

        if (this.recordHelper.getFieldStateParam(field, 'readOnly')) {
            o.readOnly = true;
        }

        if (this.recordHelper.getFieldStateParam(field, 'required') !== null) {
            o.defs.params.required = this.recordHelper.getFieldStateParam(field, 'required');
        }

        if (!readOnlyLocked && this.recordHelper.getFieldStateParam(field, 'readOnlyLocked')) {
            readOnlyLocked = true;
        }

        if (readOnlyLocked) {
            o.readOnlyLocked = readOnlyLocked;
        }

        if (this.recordHelper.hasFieldOptionList(field)) {
            o.customOptionList = this.recordHelper.getFieldOptionList(field);
        }

        if (this.recordViewObject) {
            o.validateCallback = () => this.recordViewObject.validateField(field);
        }

        const viewKey = field + 'Field';

        this.createView(viewKey, viewName, o);
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * Is tab-hidden.
     *
     * @return {boolean}
     */
    isTabHidden() {
        if (this.defs.tabNumber === -1 || typeof this.defs.tabNumber === 'undefined') {
            return false;
        }

        let parentView = this.getParentView();

        if (!parentView) {
            return this.defs.tabNumber > 0;
        }

        if (parentView && parentView.hasTabs) {
            return parentView.currentTab !== defs.tabNumber;
        }

        return false;
    }
}

export default BottomPanelView;
PK]�#�3+3+views/record/panels/side.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/record/panels/side */

import View from 'view';

/**
 * A side panel.
 */
class SidePanelView extends View {

    template = 'record/panels/side'

    /**
     * A field defs.
     *
     * @typedef module:views/record/panels/side~field
     *
     * @property {string} name
     * @property {string} [labelText] A translated label text.
     * @property {string} [view] A view name.
     * @property {boolean} [isAdditional]
     * @property {boolean} [readOnly]
     * @property {Object.<string,*>} [options] Options.
     */

    /**
     * A field list.
     *
     * @protected
     * @type {module:views/record/panels/side~field[]}
     */
    fieldList = null

    /**
     * A mode.
     *
     * @protected
     * @type {'list'|'detail'|'edit'}
     */
    mode = 'detail'

    /**
     * @protected
     * @type {module:views/record/panels-container~action[]}
     */
    actionList = null

    /**
     * @protected
     * @type {Array<module:views/record/panels-container~action|false>}
     */
    buttonList = null

    /**
     * Read-only.
     *
     * @protected
     */
    readOnly = false

    /**
     * Disable inline edit.
     *
     * @protected
     */
    inlineEditDisabled = false

    /**
     * Disable.
     *
     * @protected
     */
    disabled = false

    events = {
        /** @this SidePanelView */
        'click .action': function (e) {
            Espo.Utils.handleAction(this, e.originalEvent, e.currentTarget, {
                actionItems: [...this.buttonList, ...this.actionList],
                className: 'panel-action',
            });
        },
    }

    data() {
        return {
            fieldList: this.getFieldList(),
            hiddenFields: this.recordHelper.getHiddenFields(),
        };
    }

    init() {
        this.panelName = this.options.panelName;
        this.defs = this.options.defs || {};
        this.recordHelper = this.options.recordHelper;

        if ('disabled' in this.options) {
            this.disabled = this.options.disabled;
        }

        this.buttonList = _.clone(this.defs.buttonList || this.buttonList || []);
        this.actionList = _.clone(this.defs.actionList || this.actionList || []);

        this.fieldList = this.options.fieldList || this.fieldList || this.defs.fieldList || [];

        this.mode = this.options.mode || this.mode;

        this.readOnlyLocked = this.options.readOnlyLocked || this.readOnly;
        this.readOnly = this.readOnly || this.options.readOnly;
        this.inlineEditDisabled = this.inlineEditDisabled || this.options.inlineEditDisabled;

        this.recordViewObject = this.options.recordViewObject;
    }

    setup() {
        this.setupFields();

        this.fieldList = this.fieldList.map(d => {
            let item = d;

            if (typeof item !== 'object') {
                item = {
                    name: item,
                    viewKey: item + 'Field'
                };
            }

            item = Espo.Utils.clone(item);

            item.viewKey = item.name + 'Field';
            item.label = item.label || item.name;

            if (this.recordHelper.getFieldStateParam(item.name, 'hidden') !== null) {
                item.hidden = this.recordHelper.getFieldStateParam(item.name, 'hidden');
            } else {
                this.recordHelper.setFieldStateParam(item.name, item.hidden || false);
            }

            return item;
        });

        this.fieldList = this.fieldList.filter((item) => {
            if (!item.name) {
                return;
            }

            if (!item.isAdditional) {
                if (!(item.name in (((this.model.defs || {}).fields) || {}))) return;
            }

            return true;
        });

        this.createFields();
    }

    afterRender() {
        if (this.$el.children().length === 0) {
            this.$el.parent().addClass('hidden');
        }
    }

    /**
     * Set up fields.
     *
     * @protected
     */
    setupFields() {}

    /**
     * Create a field view.
     *
     * @protected
     * @param {string} field A field name.
     * @param {string|null} [viewName] A view name/path.
     * @param {Object<string,*>} [params] Field params.
     * @param {'detail'|'edit'|'list'|null} [mode='edit'] A mode.
     * @param {boolean} [readOnly] Read-only.
     * @param {Object<string,*>} [options] View options.
     */
    createField(field, viewName, params, mode, readOnly, options) {
        let type = this.model.getFieldType(field) || 'base';

        viewName = viewName ||
            this.model.getFieldParam(field, 'view') ||
            this.getFieldManager().getViewName(type);

        let o = {
            model: this.model,
            selector: '.field[data-name="' + field + '"]',
            defs: {
                name: field,
                params: params || {},
            },
            mode: mode || this.mode,
            dataObject: this.options.dataObject,
        };

        if (options) {
            for (let param in options) {
                o[param] = options[param];
            }
        }

        let readOnlyLocked = this.readOnlyLocked;

        if (this.readOnly) {
            o.readOnly = true;
        }
        else {
            if (readOnly !== null) {
                o.readOnly = readOnly;
            }
        }

        if (readOnly) {
            readOnlyLocked = true;
        }

        if (this.inlineEditDisabled) {
            o.inlineEditDisabled = true;
        }

        if (this.recordHelper.getFieldStateParam(field, 'hidden')) {
            o.disabled = true;
        }

        if (this.recordHelper.getFieldStateParam(field, 'hiddenLocked')) {
            o.disabledLocked = true;
        }

        if (this.recordHelper.getFieldStateParam(field, 'readOnly')) {
            o.readOnly = true;
        }

        if (this.recordHelper.getFieldStateParam(field, 'required') !== null) {
            o.defs.params.required = this.recordHelper.getFieldStateParam(field, 'required');
        }

        if (!readOnlyLocked && this.recordHelper.getFieldStateParam(field, 'readOnlyLocked')) {
            readOnlyLocked = true;
        }

        if (readOnlyLocked) {
            o.readOnlyLocked = readOnlyLocked;
        }

        if (this.recordHelper.hasFieldOptionList(field)) {
            o.customOptionList = this.recordHelper.getFieldOptionList(field);
        }

        if (this.recordViewObject) {
            o.validateCallback = () => this.recordViewObject.validateField(field);
        }

        o.recordHelper = this.recordHelper;

        let viewKey = field + 'Field';

        this.createView(viewKey, viewName, o);
    }

    /**
     * @private
     */
    createFields() {
        this.getFieldList().forEach(item => {
            let view = null;
            let field;
            let readOnly = null;

            if (typeof item === 'object') {
                field = item.name;
                view = item.view;

                if ('readOnly' in item) {
                    readOnly = item.readOnly;
                }
            }
            else {
               field = item;
            }

            if (!item.isAdditional) {
                if (!(field in this.model.defs.fields)) {
                    return;
                }
            }

            this.createField(field, view, null, null, readOnly, item.options);
        });
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * @deprecated Use `getFieldViews`.
     * @todo Remove in v9.0.
     */
    getFields() {
        return this.getFieldViews();
    }

    /**
     * Get field views.
     *
     * @return {Object.<string, module:views/fields/base>}
     */
    getFieldViews() {
        let fields = {};

        this.getFieldList().forEach(item => {
            if (this.hasView(item.viewKey)) {
                fields[item.name] = this.getView(item.viewKey);
            }
        });

        return fields;
    }

    /**
     * Get a field list.
     *
     * @return {module:views/record/panels/side~field[]}
     */
    getFieldList() {
        return this.fieldList.map(item => {
            if (typeof item !== 'object') {
                return {
                    name: item,
                };
            }

            return item;
        });
    }

    /**
     * @return {module:views/record/panels-container~action[]}
     */
    getActionList() {
        return this.actionList || [];
    }

    /**
     * @return {module:views/record/panels-container~button[]}
     */
    getButtonList() {
        return this.buttonList || [];
    }

    /**
     * A `refresh` action.
     */
    actionRefresh() {
        this.model.fetch();
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * Is tab-hidden.
     *
     * @return {boolean}
     */
    isTabHidden() {
        if (this.defs.tabNumber === -1 || typeof this.defs.tabNumber === 'undefined') {
            return false;
        }

        let parentView = this.getParentView();

        if (!parentView) {
            return this.defs.tabNumber > 0;
        }

        if (parentView && parentView.hasTabs) {
            return parentView.currentTab !== defs.tabNumber;
        }

        return false;
    }
}

export default SidePanelView;
PK]]R����#views/record/panels/default-side.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import SidePanelView from 'views/record/panels/side';

/**
 * A default side panel.
 */
class DefaultSidePanelView extends SidePanelView {

    data() {
        let data = super.data();

        if (
            this.complexCreatedDisabled &&
            this.complexModifiedDisabled || (!this.hasComplexCreated && !this.hasComplexModified)
        ) {
            data.complexDateFieldsDisabled = true;
        }

        data.hasComplexCreated = this.hasComplexCreated;
        data.hasComplexModified = this.hasComplexModified;

        return data;
    }

    setup() {
        this.fieldList = Espo.Utils.cloneDeep(this.fieldList);

        this.hasComplexCreated =
            !!this.getMetadata().get(['entityDefs', this.model.entityType, 'fields', 'createdAt']) &&
            !!this.getMetadata().get(['entityDefs', this.model.entityType, 'fields', 'createdBy']);

        this.hasComplexModified =
            !!this.getMetadata().get(['entityDefs', this.model.entityType, 'fields', 'modifiedAt']) &&
            !!this.getMetadata().get(['entityDefs', this.model.entityType, 'fields', 'modifiedBy']);

        super.setup();
    }

    setupFields() {
        super.setupFields();

        if (!this.complexCreatedDisabled) {
            if (this.hasComplexCreated) {
                this.fieldList.push({
                    name: 'complexCreated',
                    labelText: this.translate('Created'),
                    isAdditional: true,
                    view: 'views/fields/complex-created',
                    readOnly: true,
                });

                if (!this.model.get('createdById')) {
                    this.recordViewObject.hideField('complexCreated');
                }
            }
        } else {
            this.recordViewObject.hideField('complexCreated');
        }

        if (!this.complexModifiedDisabled) {
            if (this.hasComplexModified) {
                this.fieldList.push({
                    name: 'complexModified',
                    labelText: this.translate('Modified'),
                    isAdditional: true,
                    view: 'views/fields/complex-created',
                    readOnly: true,
                    options: {
                        baseName: 'modified',
                    },
                });
            }
            if (!this.model.get('modifiedById')) {
                this.recordViewObject.hideField('complexModified');
            }
        } else {
            this.recordViewObject.hideField('complexModified');
        }

        if (!this.complexCreatedDisabled && this.hasComplexCreated) {
            this.listenTo(this.model, 'change:createdById', () => {
                if (!this.model.get('createdById')) {
                    return;
                }

                this.recordViewObject.showField('complexCreated');
            });
        }

        if (!this.complexModifiedDisabled && this.hasComplexModified) {
            this.listenTo(this.model, 'change:modifiedById', () => {
                if (!this.model.get('modifiedById')) {
                    return;
                }

                this.recordViewObject.showField('complexModified');
            });
        }

        if (this.getMetadata().get(['scopes', this.model.entityType ,'stream']) && !this.getUser().isPortal()) {
            this.fieldList.push({
                name: 'followers',
                labelText: this.translate('Followers'),
                isAdditional: true,
                view: 'views/fields/followers',
                readOnly: true,
            });

            this.controlFollowersField();

            this.listenTo(this.model, 'change:followersIds', () => this.controlFollowersField());
        }
    }

    controlFollowersField() {
        if (this.model.get('followersIds') && this.model.get('followersIds').length) {
            this.recordViewObject.showField('followers');
        } else {
            this.recordViewObject.hideField('followers');
        }
    }
}

export default DefaultSidePanelView;
PK]D{views/record/edit-small.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import EditRecordView from 'views/record/edit';

class EditSmallRecordView extends EditRecordView {

    bottomView = null
}

export default EditSmallRecordView;
PK]W��(�(views/record/list-tree-item.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/record/list-tree-item */

import View from 'view';

class ListTreeRecordItemView extends View {

    template = 'record/list-tree-item'

    isEnd = false
    level = 0
    listViewName = 'views/record/list-tree'

    data() {
        return {
            name: this.model.get('name'),
            isUnfolded: this.isUnfolded,
            showFold: this.isUnfolded && !this.isEnd,
            showUnfold: !this.isUnfolded && !this.isEnd,
            isEnd: this.isEnd,
            isSelected: this.isSelected,
            readOnly: this.readOnly,
        };
    }

    events = {
        /** @this ListTreeRecordItemView */
        'click [data-action="unfold"]': function (e) {
            this.unfold();

            e.stopPropagation();
        },
        /** @this ListTreeRecordItemView */
        'click [data-action="fold"]': function (e) {
            this.fold();

            e.stopPropagation();
        },
        /** @this ListTreeRecordItemView */
        'click [data-action="remove"]': function (e) {
            this.actionRemove();

            e.stopPropagation();
        }
    }

    setIsSelected() {
        this.isSelected = true;
        this.selectedData.id = this.model.id;

        let path = this.selectedData.path;
        let names = this.selectedData.names;

        path.length = 0;

        let view = this;

        while (1) {
            path.unshift(view.model.id);
            names[view.model.id] = view.model.get('name');

            if (view.getParentListView().level) {
                view = view.getParentView().getParentView();
            } else {
                break;
            }
        }
    }

    setup() {
        if ('level' in this.options) {
            this.level = this.options.level;
        }

        if ('isSelected' in this.options) {
            this.isSelected = this.options.isSelected;
        }

        if ('selectedData' in this.options) {
            this.selectedData = this.options.selectedData;
        }

        this.readOnly = this.options.readOnly;

        if ('createDisabled' in this.options) {
            this.createDisabled = this.options.createDisabled;
        }

        if (this.readOnly) {
            this.createDisabled = true;
        }

        this.rootView = this.options.rootView;
        this.scope = this.model.entityType;

        this.isUnfolded = false;

        var childCollection = this.model.get('childCollection');

        if ((childCollection && childCollection.length === 0) || this.model.isEnd) {
            if (this.createDisabled) {
                this.isEnd = true;
            }
        }
        else if (childCollection) {
            childCollection.models.forEach(model => {
                if (~this.selectedData.path.indexOf(model.id)) {
                    this.isUnfolded = true;
                }
            });

            if (this.isUnfolded) {
                this.createChildren();
            }
        }

        this.on('select', o => {
            this.getParentListView().trigger('select', o);
        });
    }

    /**
     * @return {module:views/record/list-tree}
     */
    getParentListView() {
        return /** @type module:views/record/list-tree */this.getParentView();
    }

    createChildren() {
        let childCollection = this.model.get('childCollection');

        let callback = null;

        if (this.isRendered()) {
            callback = view => {
                this.listenToOnce(view, 'after:render', () => {
                    this.trigger('children-created');
                });

                view.render();
            };
        }

        this.createView('children', this.listViewName, {
            collection: childCollection,
            selector: '> .children',
            createDisabled: this.options.createDisabled,
            readOnly: this.options.readOnly,
            level: this.level + 1,
            selectedData: this.selectedData,
            model: this.model,
            selectable: this.options.selectable,
            rootView: this.rootView,
        }, callback);
    }

    checkLastChildren() {
        Espo.Ajax
            .getRequest(this.collection.entityType + '/action/lastChildrenIdList', {parentId: this.model.id})
            .then(idList =>{
                let childrenView = this.getChildrenView();

                idList.forEach(id => {
                    var model = this.model.get('childCollection').get(id);

                    if (model) {
                        model.isEnd = true;
                    }

                    var itemView = childrenView.getView(id);

                    if (!itemView) {
                        return;
                    }

                    itemView.isEnd = true;

                    itemView.afterIsEnd();
                });

                this.model.lastAreChecked = true;
            });
    }

    unfold() {
        if (this.createDisabled) {
            this.once('children-created', () => {
                if (!this.model.lastAreChecked) {
                    this.checkLastChildren();
                }
            });
        }

        let childCollection = this.model.get('childCollection');

        if (childCollection !== null) {
            this.createChildren();
            this.isUnfolded = true;
            this.afterUnfold();

            this.trigger('after:unfold');

            return;
        }

        this.getCollectionFactory().create(this.scope, collection => {
            collection.url = this.collection.url;
            collection.parentId = this.model.id;
            collection.maxDepth = null;

            Espo.Ui.notify(' ... ');

            this.listenToOnce(collection, 'sync', () => {
                Espo.Ui.notify(false);

                this.model.set('childCollection', collection);

                this.createChildren();

                this.isUnfolded = true;

                if (collection.length || !this.createDisabled) {
                    this.afterUnfold();

                    this.trigger('after:unfold');
                } else {
                    this.isEnd = true;

                    this.afterIsEnd();
                }
            });

            collection.fetch();
        });
    }

    fold() {
        this.clearView('children');

        this.isUnfolded = false;

        this.afterFold();
    }

    afterRender() {
        if (this.isUnfolded) {
            this.afterUnfold();
        } else {
            this.afterFold();
        }

        if (this.isEnd) {
            this.afterIsEnd();
        }

        if (!this.readOnly) {
            let $remove = this.$el.find('> .cell [data-action="remove"]');

            this.$el.find('> .cell').on('mouseenter', function () {
                $remove.removeClass('hidden');
            });

            this.$el.find('> .cell').on('mouseleave', function () {
                $remove.addClass('hidden');
            });
        }
    }

    afterFold() {
        this.$el.find('a[data-action="fold"][data-id="'+this.model.id+'"]').addClass('hidden');
        this.$el.find('a[data-action="unfold"][data-id="'+this.model.id+'"]').removeClass('hidden');
        this.$el.find(' > .children').addClass('hidden');
    }

    afterUnfold() {
        this.$el.find('a[data-action="unfold"][data-id="'+this.model.id+'"]').addClass('hidden');
        this.$el.find('a[data-action="fold"][data-id="'+this.model.id+'"]').removeClass('hidden');
        this.$el.find(' > .children').removeClass('hidden');
    }

    afterIsEnd() {
        this.$el.find('a[data-action="unfold"][data-id="'+this.model.id+'"]').addClass('hidden');
        this.$el.find('a[data-action="fold"][data-id="'+this.model.id+'"]').addClass('hidden');
        this.$el.find('span[data-name="white-space"][data-id="'+this.model.id+'"]').removeClass('hidden');
        this.$el.find(' > .children').addClass('hidden');
    }

    getCurrentPath() {
        let pointer = this;
        let path = [];

        while (true) {
            path.unshift(pointer.model.id);

            if (pointer.getParentView() === this.rootView) {
                break;
            }

            pointer = pointer.getParentView().getParentView();
        }

        return path;
    }

    actionRemove() {
        this.confirm({
            message: this.translate('removeRecordConfirmation', 'messages', this.scope),
            confirmText: this.translate('Remove'),
        }, () => {
            this.model.destroy({wait: true})
                .then(() => this.remove());

        });
    }

    /**
     * @return module:views/record/list-tree
     */
    getChildrenView() {
        return /** @type module:views/record/list-tree */this.getView('children');
    }
}

export default ListTreeRecordItemView;
PK]Uc"6views/record/detail-small.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import DetailRecordView from 'views/record/detail';

class DetailSmallRecordView extends DetailRecordView {

    bottomView = null
}

export default DetailSmallRecordView;
PK]��ąccviews/record/edit-bottom.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import DetailBottomRecordView from 'views/record/detail-bottom';

class EditBottomRecordView extends DetailBottomRecordView {

    mode = 'edit'
    streamPanel = false
    relationshipPanels = false
}

export default EditBottomRecordView;
PK]�F\�2�2�views/record/kanban.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/record/kanban */

import ListRecordView from 'views/record/list';

/**
 * A kanban record view.
 */
class KanbanRecordView extends ListRecordView {

    template = 'record/kanban'

    itemViewName = 'views/record/kanban-item'
    rowActionsView = 'views/record/row-actions/default-kanban'

    type = 'kanban'
    name = 'kanban'

    showCount = true
    headerDisabled = false
    layoutName = 'kanban'
    portalLayoutDisabled = false
    minColumnWidthPx = 220
    showMore = true
    quickDetailDisabled = false
    quickEditDisabled = false
    listLayout = null
    _internalLayout = null
    buttonsDisabled = false
    backDragStarted = true

    /**
     * A button list.
     *
     * @protected
     * @type {module:views/record/list~button[]}
     */
    buttonList = []

    events = {
         /** @this KanbanRecordView */
        'click a.link': function (e) {
            if (e.ctrlKey || e.metaKey || e.shiftKey) {
                return;
            }

            e.stopPropagation();

            if (!this.scope || this.selectable) {
                return;
            }

            e.preventDefault();

            let id = $(e.currentTarget).data('id');
            let model = this.collection.get(id);

            let scope = this.getModelScope(id);

            let options = {
                id: id,
                model: model,
            };

            if (this.options.keepCurrentRootUrl) {
                options.rootUrl = this.getRouter().getCurrentUrl();
            }

            this.getRouter().navigate('#' + scope + '/view/' + id, {trigger: false});
            this.getRouter().dispatch(scope, 'view', options);
        },
        /** @this KanbanRecordView */
        'click [data-action="groupShowMore"]': function (e) {
            let $target = $(e.currentTarget);

            let group = $target.data('name');

            this.groupShowMore(group);
        },
        /** @this KanbanRecordView */
        'click .action': function (e) {
            Espo.Utils.handleAction(this, e.originalEvent, e.currentTarget, {
                actionItems: [...this.buttonList],
                className: 'list-action-item',
            });
        },
        /** @this KanbanRecordView */
        'mouseenter th.group-header': function (e) {
            let group = $(e.currentTarget).attr('data-name');

            this.showPlus(group);
        },
        /** @this KanbanRecordView */
        'mouseleave th.group-header': function (e) {
            let group = $(e.currentTarget).attr('data-name');

            this.hidePlus(group);
        },
        /** @this KanbanRecordView */
        'click [data-action="createInGroup"]': function (e) {
            let group = $(e.currentTarget).attr('data-group');

            this.actionCreateInGroup(group);
        },
        /** @this KanbanRecordView */
        'mousedown .kanban-columns td': function (e) {
            if ($(e.originalEvent.target).closest('.item').length) {
                return;
            }

            this.initBackDrag(e.originalEvent);
        },
        /** @this KanbanRecordView */
        'auxclick a.link': function (e) {
            let isCombination = e.button === 1 && (e.ctrlKey || e.metaKey);

            if (!isCombination) {
                return;
            }

            let $target = $(e.currentTarget);

            let id = $target.attr('data-id');

            if (!id) {
                return;
            }

            if (this.quickDetailDisabled) {
                return;
            }

            let $quickView = $target.parent().closest(`[data-id="${id}"]`)
                .find(`ul.list-row-dropdown-menu[data-id="${id}"] a[data-action="quickView"]`);

            if (!$quickView.length) {
                return;
            }

            e.preventDefault();
            e.stopPropagation();

            this.actionQuickView({id: id});
        },
    }

    data() {
        return {
            scope: this.scope,
            header: this.header,
            topBar: this.displayTotalCount || this.buttonList.length && !this.buttonsDisabled,
            showCount: this.showCount && this.collection.total > 0,
            buttonList: this.buttonList,
            displayTotalCount: this.displayTotalCount && this.collection.total >= 0,
            totalCount: this.collection.total,
            statusList: this.statusList,
            groupDataList: this.groupDataList,
            minTableWidthPx: this.minColumnWidthPx * this.statusList.length,
            isEmptyList: this.collection.models.length === 0,
            totalCountFormatted: this.getNumberUtil().formatInt(this.collection.total),
            isCreatable: this.isCreatable,
        };
    }

    init() {
        this.listLayout = this.options.listLayout || this.listLayout;
        this.type = this.options.type || this.type;

        this.layoutName = this.options.layoutName || this.layoutName || this.type;

        this.rowActionsView = _.isUndefined(this.options.rowActionsView) ?
            this.rowActionsView :
            this.options.rowActionsView;

        if (this.massActionsDisabled && !this.selectable) {
            this.checkboxes = false;
        }

        this.rowActionsDisabled = this.options.rowActionsDisabled || this.rowActionsDisabled;

        if ('buttonsDisabled' in this.options) {
            this.buttonsDisabled = this.options.buttonsDisabled;
        }
    }

    /** @inheritDoc */
    getModelScope(id) {
        return this.scope;
    }

    /** @inheritDoc */
    setup() {
        if (typeof this.collection === 'undefined') {
            throw new Error('Collection has not been injected into Record.List view.');
        }

        this.layoutLoadCallbackList = [];

        this.entityType = this.collection.entityType || null;
        this.scope = this.options.scope || this.entityType;

        this.buttonList = Espo.Utils.clone(this.buttonList);

        if ('showCount' in this.options) {
            this.showCount = this.options.showCount;
        }

        this.displayTotalCount = this.showCount && this.getConfig().get('displayListViewRecordCount');

        this.minColumnWidthPx = this.getConfig().get('kanbanMinColumnWidth') || this.minColumnWidthPx;

        if ('displayTotalCount' in this.options) {
            this.displayTotalCount = this.options.displayTotalCount;
        }

        if (this.getUser().isPortal() && !this.portalLayoutDisabled) {
            if (
                this.getMetadata()
                    .get(['clientDefs', this.scope, 'additionalLayouts', this.layoutName + 'Portal'])
            ) {
                this.layoutName += 'Portal';
            }
        }

        this.orderDisabled = this.getMetadata().get(['scopes', this.scope, 'kanbanOrderDisabled']);

        if (this.getUser().isPortal()) {
            this.orderDisabled = true;
        }

        this.statusField = this.getMetadata().get(['scopes', this.scope, 'statusField']);

        if (!this.statusField) {
            throw new Error("No status field for entity type '" + this.scope + "'.");
        }

        this.statusList = Espo.Utils.clone(this.getMetadata().get(
            ['entityDefs', this.scope, 'fields', this.statusField, 'options'])
        );

        let statusIgnoreList = this.getMetadata().get(['scopes', this.scope, 'kanbanStatusIgnoreList']) || [];

        this.statusList = this.statusList.filter((item) => {
            if (~statusIgnoreList.indexOf(item)) {
                return;
            }

            return true;
        });

        this.seedCollection = this.collection.clone();
        this.seedCollection.reset();
        this.seedCollection.url = this.scope;
        this.seedCollection.maxSize = this.collection.maxSize;
        this.seedCollection.entityType = this.collection.entityType;
        this.seedCollection.orderBy = this.collection.defaultOrderBy;
        this.seedCollection.order = this.collection.defaultOrder;

        this.listenTo(this.collection, 'sync', () => {
            if (this.hasView('modal') && this.getView('modal').isRendered()) {
                return;
            }

            this.buildRows(() => {
                this.render();
            });
        });

        this.collection.listenTo(
            this.collection,
            'change:' + this.statusField,
            this.onChangeGroup.bind(this),
            this
        );

        this.buildRows();

        this.on('remove', () => {
            $(window).off('resize.kanban-a-' + this.cid);
            $(window).off('scroll.kanban-' + this.cid);
            $(window).off('resize.kanban-' + this.cid);
        });

        this.statusFieldIsEditable =
            this.getAcl().checkScope(this.entityType, 'edit') &&
            !this.getAcl().getScopeForbiddenFieldList(this.entityType, 'edit').includes(this.statusField) &&
            !this.getMetadata().get(['clientDefs', this.scope, 'editDisabled']) &&
            !this.getMetadata().get(['entityDefs', this.entityType, 'fields', this.statusField, 'readOnly']);

        this.isCreatable = this.statusFieldIsEditable && this.getAcl().check(this.entityType, 'create');

        this.wait(
            this.getHelper().processSetupHandlers(this, 'record/kanban')
        );
    }

    afterRender() {
        let $window = $(window);

        this.$listKanban = this.$el.find('.list-kanban');
        this.$content = $('#content');

        this.$groupColumnList = this.$listKanban.find('.group-column-list');

        this.$container = this.$el.find('.list-kanban-container');

        $window.off('resize.kanban-a-' + this.cid);
        $window.on('resize.kanban-a-' + this.cid, () => this.adjustMinHeight());

        this.$container.on('scroll', () => this.syncHeadScroll());

        this.adjustMinHeight();

        if (this.statusFieldIsEditable) {
            this.initSortable();
        }

        this.initStickableHeader();

        this.$showMore = this.$el.find('.group-column .show-more');

        this.plusElementMap = {};

        this.statusList.forEach(status => {
            let value = status.replace(/"/g, '\\"');

            this.plusElementMap[status] = this.$el
                .find('.kanban-head .create-button[data-group="' + value + '"]');
        });
    }

    initStickableHeader() {
        let $container = this.$headContainer = this.$el.find('.kanban-head-container');
        let topBarHeight = this.getThemeManager().getParam('navbarHeight') || 30;

        let screenWidthXs = this.getThemeManager().getParam('screenWidthXs');

        let $middle = this.$el.find('.kanban-columns-container');
        let $window = $(window);

        let $block = $('<div>')
            .addClass('kanban-head-placeholder')
            .html('&nbsp;')
            .hide()
            .insertAfter($container);

        $window.off('scroll.kanban-' + this.cid);
        $window.on('scroll.kanban-' + this.cid, () => {
            controlSticking();
        });

        $window.off('resize.kanban-' + this.cid);
        $window.on('resize.kanban-' + this.cid, () => controlSticking());

        let controlSticking = () => {
            let width = $middle.width();

            if ($(window.document).width() < screenWidthXs) {
                $container.removeClass('sticked');
                $container.css('width', '');
                $block.hide();
                $container.show();

                $container.get(0).scrollLeft = 0;
                $container.children().css('width', '');

                return;
            }

            let stickTop = this.$listKanban.offset().top - topBarHeight;

            let edge = $middle.offset().top + $middle.outerHeight(true);
            let scrollTop = $window.scrollTop();

            if (scrollTop < edge) {
                if (scrollTop > stickTop) {
                    let containerWidth = this.$container.width() - 3;

                    $container.children().css('width', width);

                    $container.css('width', containerWidth + 'px');

                    if (!$container.hasClass('sticked')) {
                        $container.addClass('sticked');
                        $block.show();
                    }
                } else {
                    $container.css('width', '');

                    if ($container.hasClass('sticked')) {
                        $container.removeClass('sticked');
                        $block.hide();
                    }
                }

                $container.show();

                this.syncHeadScroll();

                return;
            }

            $container.css('width', width + 'px');
            $container.hide();

            $block.show();

            $container.get(0).scrollLeft = 0;
            $container.children().css('width', '');
        };
    }

    initSortable() {
        let $list = this.$groupColumnList;

        $list.find('> .item').on('touchstart', (e) => {
            e.originalEvent.stopPropagation();
        });

        let orderDisabled = this.orderDisabled;

        let $groupColumnList = this.$el.find('.group-column-list');

        $list.sortable({
            distance: 10,
            connectWith: '.group-column-list',
            cancel: '.btn-group *',
            containment: this.getSelector(),
            scroll: false,
            over: function () {
                $(this).addClass('drop-hover');
            },
            out: function () {
                $(this).removeClass('drop-hover');
            },
            sort: (e) => {
                if (!this.blockScrollControl) {
                    this.controlHorizontalScroll(e.originalEvent);
                }
            },
            start: (e, ui) => {
                $groupColumnList.addClass('drop-active');

                $list.sortable('refreshPositions');

                $(ui.item)
                    .find('.btn-group.open > .dropdown-toggle')
                    .parent()
                    .removeClass('open');

                this.draggedGroupFrom = $(ui.item).closest('.group-column-list').data('name');
                this.$showMore.addClass('hidden');

                this.sortIsStarted = true;
                this.sortWasCentered = false;

                this.$draggable = ui.item;
            },
            stop: (e, ui) => {
                this.blockScrollControl = false;
                this.sortIsStarted = false;
                this.$draggable = null;

                let $item = $(ui.item);

                this.$el.find('.group-column-list').removeClass('drop-active');

                let group = $item.closest('.group-column-list').data('name');
                let id = $item.data('id');

                let draggedGroupFrom = this.draggedGroupFrom;

                this.draggedGroupFrom = null;

                this.$showMore.removeClass('hidden');

                if (group !== draggedGroupFrom) {
                    let model = this.collection.get(id);

                    if (!model) {
                        $list.sortable('cancel');

                        return;
                    }

                    let attributes = {};

                    attributes[this.statusField] = group;

                    this.handleAttributesOnGroupChange(model, attributes, group);

                    $list.sortable('disable');

                    model
                        .save(attributes, {
                            patch: true,
                            isDrop: true,
                        })
                        .then(() => {
                            Espo.Ui.success(this.translate('Saved'));

                            $list.sortable('destroy');

                            this.initSortable();

                            this.moveModelBetweenGroupCollections(model, draggedGroupFrom, group);

                            if (!orderDisabled) {
                                this.reOrderGroup(group);
                                this.storeGroupOrder(group);
                            }

                            this.rebuildGroupDataList();
                        })
                        .catch(() => {
                            $list.sortable('cancel');
                            $list.sortable('enable');
                        });

                    return;
                }

                if (orderDisabled) {
                    $list.sortable('cancel');
                    $list.sortable('enable');

                    return;
                }

                this.reOrderGroup(group);
                this.storeGroupOrder(group);
                this.rebuildGroupDataList();
            },
        });
    }

    /**
     * @param {string} group
     * @param {string} [id] Prepend. To be used after save.
     * @return {Promise}
     */
    storeGroupOrder(group, id) {
        let ids = this.getGroupOrderFromDom(group);

        if (id) {
            ids.unshift(id);
        }

        return Espo.Ajax.putRequest('Kanban/order', {
            entityType: this.entityType,
            group: group,
            ids: ids,
        });
    }

    /**
     * @param {string} group
     * @return {string[]}
     */
    getGroupOrderFromDom(group) {
        let ids = [];

        let $group = this.$el.find('.group-column-list[data-name="'+group+'"]');

        $group.children().each((i, el) => {
            ids.push($(el).data('id'));
        });

        return ids;
    }

    /**
     * @param {string} group
     */
    reOrderGroup(group) {
        let groupCollection = this.getGroupCollection(group);
        let ids = this.getGroupOrderFromDom(group);

        let modelMap = {};

        groupCollection.models.forEach((m) => {
            modelMap[m.id] = m;
        });

        while (groupCollection.models.length) {
            groupCollection.pop({silent: true});
        }

        ids.forEach(id => {
            let model = modelMap[id];

            if (!model) {
                return;
            }

            groupCollection.add(model, {silent: true});
        });
    }

    rebuildGroupDataList() {
        this.groupDataList.forEach(item => {
            item.dataList = [];

            for (let model of item.collection.models) {
                item.dataList.push({
                    key: model.id,
                    id: model.id,
                });
            }
        });
    }

    moveModelBetweenGroupCollections(model, groupFrom, groupTo) {
        let collection = this.getGroupCollection(groupFrom);

        if (!collection) {
            return;
        }

        collection.remove(model.id, {silent: true});

        collection = this.getGroupCollection(groupTo);

        if (!collection) {
            return;
        }

        collection.add(model, {silent: true});
    }

    handleAttributesOnGroupChange(model, attributes, group) {}

    adjustMinHeight() {
        if (
            this.collection.models.length === 0 ||
            !this.$container
        ) {
            return;
        }

        let height = this.getHelper()
            .calculateContentContainerHeight(this.$el.find('.kanban-columns-container'));

        let containerEl = this.$container.get(0);

        if (containerEl.scrollWidth > containerEl.clientWidth) {
            height -= 18;
        }

        if (height < 100) {
            height = 100;
        }

        this.$listKanban.find('td.group-column').css({
            minHeight: height + 'px',
        });
    }

    getListLayout(callback) {
        if (this.listLayout) {
            callback.call(this, this.listLayout);

            return;
        }

        this._loadListLayout((listLayout) => {
            this.listLayout = listLayout;
            callback.call(this, listLayout);
        });
    }

    getSelectAttributeList(callback) {
        super.getSelectAttributeList(attributeList => {
            if (attributeList) {
                if (!~attributeList.indexOf(this.statusField)) {
                    attributeList.push(this.statusField);
                }
            }

            callback(attributeList);
        });
    }

    buildRows(callback) {
        let groupList = (this.collection.dataAdditional || {}).groupList || [];

        this.collection.reset();

        this.collection.subCollectionList = [];

        this.wait(true);

        this.groupDataList = [];

        let count = 0;
        let loadedCount = 0;

        this.getListLayout((listLayout) => {
            this.listLayout = listLayout;

            groupList.forEach((item, i) => {
                let collection = this.seedCollection.clone();

                this.listenTo(collection, 'destroy', (model, attributes, o) => {
                    if (o.fromList) {
                        return;
                    }

                    this.removeRecordFromList(model.id);
                });

                collection.total = item.total;

                collection.url = this.collection.url;
                collection.where = this.collection.where;

                collection.entityType = this.seedCollection.entityType;
                collection.maxSize = this.seedCollection.maxSize;
                collection.orderBy = this.seedCollection.orderBy;
                collection.order = this.seedCollection.order;

                collection.whereAdditional = [
                    {
                        field: this.statusField,
                        type: 'equals',
                        value: item.name,
                    }
                ];

                collection.groupName = item.name;
                collection.set(item.list);

                this.collection.subCollectionList.push(collection);

                this.collection.add(collection.models);

                let itemDataList = [];

                collection.models.forEach(model => {
                    count ++;

                    itemDataList.push({
                        key: model.id,
                        id: model.id,
                    });
                });

                let nextStyle = null;

                if (i < groupList.length - 1) {
                    nextStyle = this.getMetadata()
                        .get(['entityDefs', this.scope, 'fields', this.statusField,
                            'style', groupList[i + 1].name]);
                }

                let o = {
                    name: item.name,
                    label: this.getLanguage().translateOption(item.name, this.statusField, this.scope),
                    dataList: itemDataList,
                    collection: collection,
                    isLast: i === groupList.length - 1,
                    hasShowMore: collection.total > collection.length || collection.total === -1,
                    style: this.getMetadata().get(
                        ['entityDefs', this.scope, 'fields', this.statusField, 'style', item.name]
                    ),
                    nextStyle: nextStyle,
                };

                this.groupDataList.push(o);
            });

            if (count === 0) {
                this.wait(false);

                if (callback) {
                    callback();
                }

                return;
            }

            this.groupDataList.forEach(groupItem => {
                groupItem.dataList.forEach((item, j) => {
                    let model = groupItem.collection.get(item.id);

                    this.buildRow(j, model, () => {
                        loadedCount++;

                        if (loadedCount === count) {
                            this.wait(false);

                            if (callback) {
                                callback();
                            }
                        }
                    });
                });
            });
        });
    }

    buildRow(i, model, callback) {
        let key = model.id;

        this.createView(key, this.itemViewName, {
            model: model,
            selector: '.item[data-id="'+model.id+'"]',
            itemLayout: this.listLayout,
            rowActionsDisabled: this.rowActionsDisabled,
            rowActionsView: this.rowActionsView,
            setViewBeforeCallback: this.options.skipBuildRows && !this.isRendered(),
            statusFieldIsEditable: this.statusFieldIsEditable,
        }, callback);
    }

    removeRecordFromList(id) {
        this.collection.remove(id);

        if (this.collection.total > 0) {
            this.collection.total--;
        }

        this.totalCount = this.collection.total;

        this.$el.find('.total-count-span').text(this.totalCount.toString());

        this.clearView(id);

        this.$el.find('.item[data-id="'+id+'"]').remove();

        this.collection.subCollectionList.forEach(collection => {
            if (collection.get(id)) {
                collection.remove(id);
            }
        });

        for (let groupItem of this.groupDataList) {
            for (let j = 0; j < groupItem.dataList.length; j++) {
                let item = groupItem.dataList[j];

                if (item.id !== id) {
                    continue;
                }

                groupItem.dataList.splice(j, 1);

                if (groupItem.collection.total > 0) {
                    groupItem.collection.total--;
                }

                groupItem.hasShowMore = groupItem.collection.total > groupItem.collection.length ||
                    groupItem.collection.total === -1;

                break;
            }
        }
    }

    onChangeGroup(model, value, o) {
        let id = model.id;
        let group = model.get(this.statusField);

        this.collection.subCollectionList.forEach((collection) => {
            if (collection.get(id)) {
                collection.remove(id);

                if (collection.total > 0) {
                    collection.total--;
                }
            }
        });

        let dataItem;

        for (let groupItem of this.groupDataList) {
            for (let j = 0; j < groupItem.dataList.length; j++) {
                let item = groupItem.dataList[j];

                if (item.id === id) {
                    dataItem = item;
                    groupItem.dataList.splice(j, 1);

                    break;
                }
            }
        }

        if (!group) {
            return;
        }

        if (o.isDrop) {
            return;
        }

        for (let groupItem of this.groupDataList) {
            if (groupItem.name !== group) {
                continue;
            }

            groupItem.collection.unshift(model);
            groupItem.collection.total++;

            if (dataItem) {
                groupItem.dataList.unshift(dataItem);

                groupItem.hasShowMore = groupItem.collection.total > groupItem.collection.length ||
                    groupItem.collection.total === -1;
            }
        }

        let $item = this.$el.find('.item[data-id="' + id + '"]');
        let $column = this.$el.find('.group-column[data-name="' + group + '"] .group-column-list');

        if ($column.length) {
            $column.prepend($item);
        } else {
            $item.remove();
        }

        if (!this.orderDisabled) {
            this.storeGroupOrder(group);
        }
    }

    groupShowMore(group) {
        let groupItem;

        for (let i in this.groupDataList) {
            groupItem = this.groupDataList[i];

            if (groupItem.name === group) {
                break;
            }

            groupItem = null;
        }

        if (!groupItem) {
            return;
        }

        let collection = groupItem.collection;

        let $list = this.$el.find('.group-column-list[data-name="'+group+'"]');
        let $showMore = this.$el.find('.group-column[data-name="'+group+'"] .show-more');

        collection.data.select = this.collection.data.select;

        this.showMoreRecords({}, collection, $list, $showMore, () => {
            this.noRebuild = false;

            collection.models.forEach((model) => {
                if (this.collection.get(model.id)) {
                    return;
                }

                this.collection.add(model);

                groupItem.dataList.push({
                    key: model.id,
                    id: model.id,
                });
            });
        });
    }

    getDomRowItem(id) {
        return this.$el.find('.item[data-id="'+id+'"]');
    }

    getRowContainerHtml(id) {
        return $('<div>')
            .attr('data-id', id)
            .addClass('item')
            .get(0).outerHTML;
    }

    // noinspection JSUnusedGlobalSymbols
    actionMoveOver(data) {
        let model = this.collection.get(data.id);

        this.createView('moveOverDialog', 'views/modals/kanban-move-over', {
            model: model,
            statusField: this.statusField,
        }, view => {
            view.render();
        });
    }

    /**
     *
     * @param {string} group
     * @return {module:collection}
     */
    getGroupCollection(group) {
        let collection = null;

        this.collection.subCollectionList.forEach(itemCollection => {
            if (itemCollection.groupName === group) {
                collection = itemCollection;
            }
        });

        return collection;
    }

    /**
     * @param {string} group
     */
    showPlus(group) {
        let $el = this.plusElementMap[group];

        if (!$el) {
            return;
        }

        $el.removeClass('hidden');
    }

    /**
     * @param {string} group
     */
    hidePlus(group) {
        let $el = this.plusElementMap[group];

        if (!$el) {
            return;
        }

        $el.addClass('hidden');
    }

    /**
     * @param {string} group
     */
    actionCreateInGroup(group) {
        let attributes = {};

        attributes[this.statusField] = group;

        let viewName = this.getMetadata().get('clientDefs.' + this.scope + '.modalViews.edit') ||
            'views/modals/edit';

        let options = {
            attributes: attributes,
            scope: this.scope,
        };

        this.createView('quickCreate', viewName, options, /** module:views/modals/edit */view => {
            view.getRecordView().setFieldReadOnly(this.statusField, true);

            view.render();

            this.listenToOnce(view, 'after:save', () => {
                if (this.orderDisabled) {
                    this.collection.fetch();

                    return;
                }

                this.storeGroupOrder(group, view.model.id)
                    .then(() => this.collection.fetch());
            });
        });
    }

    initBackDrag(e) {
        this.backDragStarted = true;

        let containerEl = this.$container.get(0);

        containerEl.style.cursor = 'grabbing';
        containerEl.style.userSelect = 'none';

        let $document = $(document);

        let startLeft = containerEl.scrollLeft;
        let startX = e.clientX;

        $document.on('mousemove.' + this.cid, (e) => {
            let dx = e.originalEvent.clientX - startX;

            containerEl.scrollLeft = startLeft - dx;

            this.syncHeadScroll();
        });

        $document.one('mouseup.' + this.cid, () => {
            this.stopBackDrag();
        });
    }

    stopBackDrag() {
        this.$container.get(0).style.cursor = 'default';
        this.$container.get(0).style.userSelect = 'none';

        $(document).off('mousemove.' + this.cid);
    }

    syncHeadScroll() {
        if (!this.$headContainer.hasClass('sticked')) {
            return;
        }

        this.$headContainer.get(0).scrollLeft = this.$container.get(0).scrollLeft;
    }

    controlHorizontalScroll(e) {
        if (!this.sortIsStarted) {
            return;
        }

        if (!this.$draggable) {
            return;
        }

        let draggableRect = this.$draggable.get(0).getBoundingClientRect();

        let itemLeft = draggableRect.left;
        let itemRight = draggableRect.right;

        let containerEl = this.$container.get(0);

        let rect = containerEl.getBoundingClientRect();

        let marginSens = 70;
        let step = 2;
        let interval = 5;
        let marginSensStepRatio = 4;
        let stepRatio = 3;

        let isRight = rect.right - marginSens < itemRight &&
            containerEl.scrollLeft + containerEl.offsetWidth < containerEl.scrollWidth;

        let isLeft = rect.left + marginSens > itemLeft &&
            containerEl.scrollLeft > 0;

        this.$groupColumnList.sortable('refreshPositions');

        if (isRight && this.sortWasCentered) {
            let margin = rect.right - itemRight;

            if (margin < marginSens / marginSensStepRatio) {
                step *= stepRatio;
            }

            let stepActual = Math.min(step, containerEl.offsetWidth - containerEl.scrollLeft);

            containerEl.scrollLeft = containerEl.scrollLeft + stepActual;

            this.syncHeadScroll();

            if (containerEl.scrollLeft + containerEl.offsetWidth === containerEl.scrollWidth) {
                this.blockScrollControl = false;

                return;
            }

            this.blockScrollControl = true;

            setTimeout(() => this.controlHorizontalScroll(e), interval);

            return;
        }

        if (isLeft && this.sortWasCentered) {
            let margin = - (rect.left - itemLeft);

            if (margin < marginSens / marginSensStepRatio) {
                step *= stepRatio;
            }

            let stepActual = Math.min(step, containerEl.scrollLeft);

            containerEl.scrollLeft = containerEl.scrollLeft - stepActual;

            this.syncHeadScroll();

            if (containerEl.scrollLeft === 0) {
                this.blockScrollControl = false;

                return;
            }

            this.blockScrollControl = true;

            setTimeout(() => this.controlHorizontalScroll(e), interval);

            return;
        }

        if (this.blockScrollControl && !isLeft && !isRight) {
            this.blockScrollControl = false;
        }

        if (!isLeft && !isRight) {
            this.sortWasCentered = true;
        }
    }
}

export default KanbanRecordView;
PK]=�b�
�
*views/record/row-actions/default-kanban.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import DefaultRowActionsView from 'views/record/row-actions/default';

class DefaultKanbanRowActionsView extends DefaultRowActionsView {

    getActionList() {
        const list = [{
            action: 'quickView',
            label: 'View',
            data: {
                id: this.model.id,
            },
            link: '#' + this.model.entityType + '/view/' + this.model.id,
        }];

        if (this.options.statusFieldIsEditable) {
            list.push({
                action: 'moveOver',
                label: 'Move Over',
                data: {
                    id: this.model.id,
                },
            });
        }

        if (this.options.acl.edit) {
            list.push({
                action: 'quickEdit',
                label: 'Edit',
                data: {
                    id: this.model.id
                },
                link: '#' + this.model.entityType + '/edit/' + this.model.id,
            });
        }

        if (this.options.acl.delete) {
            list.push({
                action: 'quickRemove',
                label: 'Remove',
                data: {
                    id: this.model.id,
                },
            });
        }

        return list;
    }
}

export default DefaultKanbanRowActionsView;
PK]OIm��+views/record/row-actions/view-and-remove.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import DefaultRowActionsView from 'views/record/row-actions/default';

class ViewAndRemoveRowActionsView extends DefaultRowActionsView {

    getActionList() {
        /** @type module:views/record/list~rowAction[] */
        const actionList = [{
            action: 'quickView',
            label: 'View',
            data: {
                id: this.model.id,
            },
            link: '#' + this.model.entityType + '/view/' + this.model.id,
        }];

        if (this.options.acl.delete) {
            actionList.push({
                action: 'quickRemove',
                label: 'Remove',
                data: {
                    id: this.model.id,
                },
            });
        }

        return actionList;
    }
}

export default ViewAndRemoveRowActionsView;
PK]鶰ؾ
�
(views/record/row-actions/relationship.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import DefaultRowActionsView from 'views/record/row-actions/default';

class RelationshipActionsView extends DefaultRowActionsView {

    getActionList() {
        const list = [{
            action: 'quickView',
            label: 'View',
            data: {
                id: this.model.id
            },
            link: '#' + this.model.entityType + '/view/' + this.model.id,
        }];

        if (this.options.acl.edit) {
            list.push({
                action: 'quickEdit',
                label: 'Edit',
                data: {
                    id: this.model.id,
                },
                link: '#' + this.model.entityType + '/edit/' + this.model.id,
            });

            if (!this.options.unlinkDisabled) {
                list.push({
                    action: 'unlinkRelated',
                    label: 'Unlink',
                    data: {
                        id: this.model.id,
                    },
                });
            }
        }

        if (this.options.acl.delete) {
            list.push({
                action: 'removeRelated',
                label: 'Remove',
                data: {
                    id: this.model.id,
                },
            });
        }

        return list;
    }
}

export default RelationshipActionsView;
PK]��|

2views/record/row-actions/relationship-no-unlink.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import RelationshipActionsView from 'views/record/row-actions/relationship';

class RelationshipNoUnlinkActionsView extends RelationshipActionsView {

    getActionList() {
        let list = [{
            action: 'quickView',
            label: 'View',
            data: {
                id: this.model.id,
            },
            link: '#' + this.model.entityType + '/view/' + this.model.id,
        }];

        if (this.options.acl.edit) {
            list = list.concat([
                {
                    action: 'quickEdit',
                    label: 'Edit',
                    data: {
                        id: this.model.id
                    },
                    link: '#' + this.model.entityType + '/edit/' + this.model.id,
                }
            ]);
        }

        if (this.options.acl.delete) {
            list.push({
                action: 'removeRelated',
                label: 'Remove',
                data: {
                    id: this.model.id,
                },
            });
        }

        return list;
    }
}

// noinspection JSUnusedGlobalSymbols
export default RelationshipNoUnlinkActionsView;
PK]��C���6views/record/row-actions/relationship-view-and-edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import RelationshipActionsView from 'views/record/row-actions/relationship';

class RelationshipViewAndEditActionsView extends RelationshipActionsView {

    getActionList() {
        const list = [{
            action: 'quickView',
            label: 'View',
            data: {
                id: this.model.id,
            },
            link: '#' + this.model.entityType + '/view/' + this.model.id,
        }];

        if (this.options.acl.edit) {
            list.push({
                action: 'quickEdit',
                label: 'Edit',
                data: {
                    id: this.model.id,
                },
                link: '#' + this.model.entityType + '/edit/' + this.model.id,
            });
        }

        return list;
    }
}

export default RelationshipViewAndEditActionsView;

PK]�[{�
�
#views/record/row-actions/default.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';

/**
 * Row actions.
 *
 * @todo The ability to define row actions in metadata. For main list view, relationship panels.
 */
class DefaultRowActionsView extends View {

    template ='record/row-actions/default'

    setup() {
        this.options.acl = this.options.acl || {};
    }

    afterRender() {
        let $dd = this.$el.find('button[data-toggle="dropdown"]').parent();

        let isChecked = false;

        $dd.on('show.bs.dropdown', () => {
            let $el = this.$el.closest('.list-row');

            isChecked = false;

            if ($el.hasClass('active')) {
                isChecked = true;
            }

            $el.addClass('active');
        });

        $dd.on('hide.bs.dropdown', () => {
            if (!isChecked) {
                this.$el.closest('.list-row').removeClass('active');
            }
        });
    }

    /**
     * Get an action list.
     *
     * @return {module:views/record/list~rowAction[]}
     */
    getActionList() {
        let list = [{
            action: 'quickView',
            label: 'View',
            data: {
                id: this.model.id
            },
            link: '#' + this.model.entityType + '/view/' + this.model.id,
        }];

        if (this.options.acl.edit) {
            list.push({
                action: 'quickEdit',
                label: 'Edit',
                data: {
                    id: this.model.id
                },
                link: '#' + this.model.entityType + '/edit/' + this.model.id,
            });
        }

        if (this.options.acl.delete) {
            list.push({
                action: 'quickRemove',
                label: 'Remove',
                data: {
                    id: this.model.id,
                }
            });
        }

        return list;
    }

    data() {
        return {
            acl: this.options.acl,
            actionList: this.getActionList(),
            scope: this.model.entityType,
        };
    }
}

export default DefaultRowActionsView;
PK]�#���8views/record/row-actions/relationship-view-and-unlink.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import RelationshipActionsView from 'views/record/row-actions/relationship';

class RelationshipViewAndUnlinkActionsView extends RelationshipActionsView {

    getActionList() {
        const list = [{
            action: 'quickView',
            label: 'View',
            data: {
                id: this.model.id,
            },
            link: '#' + this.model.entityType + '/view/' + this.model.id,
        }];

        if (this.options.acl.edit && !this.options.unlinkDisabled) {
            list.push({
                action: 'unlinkRelated',
                label: 'Unlink',
                data: {
                    id: this.model.id,
                },
            });
        }

        return list;
    }
}

export default RelationshipViewAndUnlinkActionsView;
PK]
����2views/record/row-actions/relationship-view-only.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import RelationshipActionsView from 'views/record/row-actions/relationship';

class RelationshipViewOnlyActionsView extends RelationshipActionsView {

    getActionList() {
        return [
            {
                action: 'viewRelated',
                label: 'View',
                data: {
                    id: this.model.id,
                },
                link: '#' + this.model.entityType + '/view/' + this.model.id,
            }
        ];
    }
}

export default RelationshipViewOnlyActionsView;
PK]�?l%
%
2views/record/row-actions/relationship-no-remove.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/record/row-actions/relationship-no-remove', ['views/record/row-actions/relationship'], function (Dep) {

    return Dep.extend({

        getActionList: function () {
            var list = [{
                action: 'quickView',
                label: 'View',
                data: {
                    id: this.model.id
                },
                link: '#' + this.model.entityType + '/view/' + this.model.id
            }];

            if (this.options.acl.edit) {
                list.push({
                    action: 'quickEdit',
                    label: 'Edit',
                    data: {
                        id: this.model.id
                    },
                    link: '#' + this.model.entityType + '/edit/' + this.model.id
                });
                if (!this.options.unlinkDisabled) {
                    list.push({
                        action: 'unlinkRelated',
                        label: 'Unlink',
                        data: {
                            id: this.model.id
                        }
                    });
                }
            }

            return list;
        },

    });
});
PK]LPcdd%views/record/row-actions/view-only.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import DefaultRowActionsView from 'views/record/row-actions/default';

class ViewOnlyRowActionsView extends DefaultRowActionsView {

    getActionList() {
        return [
            {
                action: 'quickView',
                label: 'View',
                data: {
                    id: this.model.id,
                },
                link: '#' + this.model.entityType + '/view/' + this.model.id,
            },
        ];
    }
}

export default ViewOnlyRowActionsView;
PK]��k%��4views/record/row-actions/relationship-unlink-only.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import RelationshipActionsView from 'views/record/row-actions/relationship';

class RelationshipUnlinkOnlyActionsView extends RelationshipActionsView {

    getActionList() {
        if (this.options.acl.edit && !this.options.unlinkDisabled) {
            return [
                {
                    action: 'unlinkRelated',
                    label: 'Unlink',
                    data: {
                        id: this.model.id,
                    },
                },
            ];
        }
    }
}

// noinspection JSUnusedGlobalSymbols
export default RelationshipUnlinkOnlyActionsView;
PK]�Иʺ�+views/record/row-actions/edit-and-remove.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import DefaultRowActionsView from 'views/record/row-actions/default';

class EditAndRemoveRowActionsView extends DefaultRowActionsView {

    getActionList() {
        let list = [];

        if (this.options.acl.edit) {
            list.push({
                action: 'quickEdit',
                label: 'Edit',
                data: {
                    id: this.model.id
                },
                link: '#' + this.model.entityType + '/edit/' + this.model.id
            });
        }

        if (this.options.acl.delete) {
            list.push({
                action: 'quickRemove',
                label: 'Remove',
                data: {
                    id: this.model.id,
                },
            });
        }

        return list;
    }
}

export default EditAndRemoveRowActionsView;
PK]<����)views/record/row-actions/view-and-edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import DefaultRowActionsView from 'views/record/row-actions/default';

class ViewAndEditRowActionsView extends DefaultRowActionsView {

    getActionList() {
        let list = [{
            action: 'quickView',
            label: 'View',
            data: {
                id: this.model.id,
            },
            link: '#' + this.model.entityType + '/view/' + this.model.id,
        }];

        if (this.options.acl.edit) {
            list = list.concat([
                {
                    action: 'quickEdit',
                    label: 'Edit',
                    data: {
                        id: this.model.id,
                    },
                    link: '#' + this.model.entityType + '/edit/' + this.model.id,
                }
            ]);
        }

        return list;
    }
}

export default ViewAndEditRowActionsView;
PK]�l�pss'views/record/row-actions/remove-only.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import DefaultRowActionsView from 'views/record/row-actions/default';

class RemoveOnlyRowActionsView extends DefaultRowActionsView {

    getActionList() {
        if (this.options.acl.delete) {
            return [
                {
                    action: 'quickRemove',
                    label: 'Remove',
                    data: {
                        id: this.model.id,
                    },
                }
            ];
        }
    }
}

export default RemoveOnlyRowActionsView;
PK]��l��8views/record/row-actions/relationship-edit-and-remove.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import RelationshipActionsView from 'views/record/row-actions/relationship';

class RelationshipEditAndRemoveActionsView extends RelationshipActionsView {

    getActionList() {
        const list = [];

        if (this.options.acl.edit) {
            list.push({
                action: 'quickEdit',
                label: 'Edit',
                data: {
                    id: this.model.id,
                },
            });
        }

        if (this.options.acl.delete) {
            list.push({
                action: 'quickRemove',
                label: 'Remove',
                data: {
                    id: this.model.id,
                },
            });
        }

        return list;
    }
}

export default RelationshipEditAndRemoveActionsView;
PK]�|@���4views/record/row-actions/relationship-remove-only.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import RelationshipActionsView from 'views/record/row-actions/relationship';

class RelationshipRemoveOnlyActionsView extends RelationshipActionsView {

    getActionList() {
        if (this.options.acl.delete) {
            return [
                {
                    action: 'removeRelated',
                    label: 'Remove',
                    data: {
                        id: this.model.id,
                    },
                },
            ];
        }
    }
}

// noinspection JSUnusedGlobalSymbols
export default RelationshipRemoveOnlyActionsView;
PK]#�KK!views/record/row-actions/empty.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import DefaultRowActionsView from 'views/record/row-actions/default';

class EmptyRowActionsView extends DefaultRowActionsView {

    getActionList() {
        return [];
    }
}

export default EmptyRowActionsView;
PK]x�_
ggviews/record/list-pagination.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/record/list-pagination', ['view'], function (Dep) {

    return Dep.extend({

        template: 'record/list-pagination',

        data: function () {
            var previous = this.collection.offset > 0;
            var next = this.collection.total - this.collection.offset > this.collection.maxSize ||
                this.collection.total === -1;

            return {
                total: this.collection.total,
                from: this.collection.offset + 1 ,
                to: this.collection.offset + this.collection.length,
                previous: previous,
                next: next,
                noTotal: this.collection.total === -1 || this.collection.total === -2,
            };
        },

    });
});
PK]/Z�
�
views/record/kanban-item.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';

class KanbanRecordItem extends View {

    template = 'record/kanban-item'

    data() {
        return {
            layoutDataList: this.layoutDataList,
            rowActionsDisabled: this.rowActionsDisabled,
        };
    }

    events = {}

    setup() {
        this.itemLayout = this.options.itemLayout;
        this.rowActionsView = this.options.rowActionsView;
        this.rowActionsDisabled = this.options.rowActionsDisabled;

        this.layoutDataList = [];

        this.itemLayout.forEach((item, i) => {
            let name = item.name;
            let key = name + 'Field';

            let o = {
                name: name,
                isAlignRight: item.align === 'right',
                isLarge: item.isLarge,
                isFirst: i === 0,
                key: key,
            };

            this.layoutDataList.push(o);

            var viewName = item.view || this.model.getFieldParam(name, 'view');
            if (!viewName) {
                var type = this.model.getFieldType(name) || 'base';
                viewName = this.getFieldManager().getViewName(type);
            }

            let mode = 'list';

            if (item.link) {
                mode = 'listLink';
            }

            this.createView(key, viewName, {
                model: this.model,
                name: name,
                mode: mode,
                readOnly: true,
                selector: '.field[data-name="'+name+'"]',
            });
        });

        if (!this.rowActionsDisabled) {
            let acl =  {
                edit: this.getAcl().checkModel(this.model, 'edit'),
                delete: this.getAcl().checkModel(this.model, 'delete'),
            };

            this.createView('itemMenu', this.rowActionsView, {
                selector: '.item-menu-container',
                model: this.model,
                acl: acl,
                statusFieldIsEditable: this.options.statusFieldIsEditable,
            });
        }
    }
}

export default KanbanRecordItem;
PK]�Af7LLviews/record/detail-middle.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/record/detail-middle */

import View from 'view';

/**
 * A detail-middle record view.
 */
class DetailMiddleRecordView extends View {

    init() {
        this.recordHelper = this.options.recordHelper;
        this.scope = this.model.entityType;
    }

    data() {
        return {
            hiddenPanels: this.recordHelper.getHiddenPanels(),
            hiddenFields: this.recordHelper.getHiddenFields(),
        };
    }

    /**
     * Show a panel.
     *
     * @param {string} name
     */
    showPanel(name) {
        if (this.recordHelper.getPanelStateParam(name, 'hiddenLocked')) {
            return;
        }

        this.showPanelInternal(name);

        this.recordHelper.setPanelStateParam(name, 'hidden', false);
    }

    /**
     * @param {string} name
     */
    showPanelInternal(name) {
        if (this.isRendered()) {
            this.$el.find('.panel[data-name="'+name+'"]').removeClass('hidden');
        }

        let wasShown = !this.recordHelper.getPanelStateParam(name, 'hidden');

        if (
            !wasShown &&
            this.options.panelFieldListMap &&
            this.options.panelFieldListMap[name]
        ) {
            this.options.panelFieldListMap[name].forEach(field => {
                var view = this.getFieldView(field);

                if (!view) {
                    return;
                }

                view.reRender();
            });
        }
    }

    /**
     * Hide a panel.
     *
     * @param {string} name
     */
    hidePanel(name) {
        this.hidePanelInternal(name);

        this.recordHelper.setPanelStateParam(name, 'hidden', true);
    }

    /**
     * @public
     * @param {string} name A name.
     */
    hidePanelInternal(name) {
        if (this.isRendered()) {
            this.$el.find('.panel[data-name="'+name+'"]').addClass('hidden');
        }
    }

    /**
     * Hide a field.
     *
     * @param {string} name A name.
     */
    hideField(name) {
        this.recordHelper.setFieldStateParam(name, 'hidden', true);

        var processHtml = () => {
            var fieldView = this.getFieldView(name);

            if (fieldView) {
                var $field = fieldView.$el;
                var $cell = $field.closest('.cell[data-name="' + name + '"]');
                var $label = $cell.find('label.control-label[data-name="' + name + '"]');

                $field.addClass('hidden');
                $label.addClass('hidden');
                $cell.addClass('hidden-cell');
            }
            else {
                this.$el.find('.cell[data-name="' + name + '"]').addClass('hidden-cell');
                this.$el.find('.field[data-name="' + name + '"]').addClass('hidden');
                this.$el.find('label.control-label[data-name="' + name + '"]').addClass('hidden');
            }
        };

        if (this.isRendered()) {
            processHtml();
        }
        else {
            this.once('after:render', () => {
                processHtml();
            });
        }

        var view = this.getFieldView(name);

        if (view) {
            view.setDisabled();
        }
    }

    /**
     * Show a field.
     *
     * @param {string} name A name.
     */
    showField(name) {
        if (this.recordHelper.getFieldStateParam(name, 'hiddenLocked')) {
            return;
        }

        this.recordHelper.setFieldStateParam(name, 'hidden', false);

        var processHtml = () => {
            var fieldView = this.getFieldView(name);

            if (fieldView) {
                var $field = fieldView.$el;
                var $cell = $field.closest('.cell[data-name="' + name + '"]');
                var $label = $cell.find('label.control-label[data-name="' + name + '"]');

                $field.removeClass('hidden');
                $label.removeClass('hidden');
                $cell.removeClass('hidden-cell');
            }
            else {
                this.$el.find('.cell[data-name="' + name + '"]').removeClass('hidden-cell');
                this.$el.find('.field[data-name="' + name + '"]').removeClass('hidden');
                this.$el.find('label.control-label[data-name="' + name + '"]').removeClass('hidden');
            }
        };

        if (this.isRendered()) {
            processHtml();
        }
        else {
            this.once('after:render', () => {
                processHtml();
            });
        }

        var view = this.getFieldView(name);

        if (view) {
            if (!view.disabledLocked) {
                view.setNotDisabled();
            }
        }
    }

    /**
     * @deprecated Use `getFieldViews`.
     */
    getFields() {
        return this.getFieldViews();
    }

    /**
     * Get field views.
     *
     * @return {Object.<string, module:views/fields/base>}
     */
    getFieldViews() {
        let fieldViews = {};

        for (let viewKey in this.nestedViews) {
            let name = this.nestedViews[viewKey].name;

            fieldViews[name] = this.nestedViews[viewKey];
        }

        return fieldViews;
    }

    /**
     * Get a field view.
     *
     * @param {string} name A field name.
     * @return {module:views/fields/base}
     */
    getFieldView(name) {
        return (this.getFieldViews() || {})[name];
    }

    /**
     * For backward compatibility.
     *
     * @todo Remove.
     */
    getView(name) {
        let view = super.getView(name);

        if (!view) {
            view = this.getFieldView(name);
        }

        return view;
    }
}

// noinspection JSUnusedGlobalSymbols
export default DetailMiddleRecordView;
PK]}weeviews/record/edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/record/edit */

import DetailRecordView from 'views/record/detail';

/**
 * An edit-record view. Used for create and edit.
 */
class EditRecordView extends DetailRecordView {

    /** @inheritDoc */
    template = 'record/edit'

    /** @inheritDoc */
    type = 'edit'
    /** @inheritDoc */
    fieldsMode = 'edit'
    /** @inheritDoc */
    mode = 'edit'
    /** @inheritDoc */
    buttonList = [
        {
            name: 'save',
            label: 'Save',
            style: 'primary',
            title: 'Ctrl+Enter',
        },
        {
            name: 'cancel',
            label: 'Cancel',
            title: 'Esc',
        }
    ]
    /** @inheritDoc */
    dropdownItemList = []
    /** @inheritDoc */
    sideView = 'views/record/edit-side'
    /** @inheritDoc */
    bottomView = 'views/record/edit-bottom'
    /** @inheritDoc */
    duplicateAction = false
    /** @inheritDoc */
    saveAndContinueEditingAction = true
    /** @inheritDoc */
    saveAndNewAction = true
    /** @inheritDoc */
    setupHandlerType = 'record/edit'

    /**
     * @param {
     *     module:views/record/detail~options |
     *     {
     *         duplicateSourceId?: string,
     *         focusForCreate?: boolean,
     *     }
     * } options Options.
     */
    constructor(options) {
        super(options);
    }

    /** @inheritDoc */
    actionSave(data) {
        data = data || {};

        let isNew = this.isNew;

        return this.save(data.options)
            .then(() => {
                if (this.options.duplicateSourceId) {
                    this.returnUrl = null;
                }

                this.exit(isNew ? 'create' : 'save');
            })
            .catch(reason => Promise.reject(reason));
    }

    /**
     * A `cancel` action.
     */
    actionCancel() {
        this.cancel();
    }

    /**
     * Cancel.
     */
    cancel() {
        if (this.isChanged) {
            this.resetModelChanges();
        }

        this.setIsNotChanged();
        this.exit('cancel');
    }

    /** @inheritDoc */
    setupBeforeFinal() {
        if (this.model.isNew()) {
            this.populateDefaults();
        }

        super.setupBeforeFinal();

        if (this.model.isNew()) {
            this.once('after:render', () => {
                this.model.set(this.fetch(), {silent: true});
            })
        }

        if (this.options.focusForCreate) {
            this.once('after:render', () => {
                if (this.$el.closest('.modal').length) {
                    setTimeout(() => this.focusForCreate(), 50);

                    return;
                }

                this.focusForCreate();
            });
        }
    }

    /** @inheritDoc */
    setupActionItems() {
        super.setupActionItems();

        if (
            this.saveAndContinueEditingAction &&
            this.getAcl().checkScope(this.entityType, 'edit')
        ) {
            this.dropdownItemList.push({
                name: 'saveAndContinueEditing',
                label: 'Save & Continue Editing',
                title: 'Ctrl+S',
            });
        }

        if (
            this.isNew &&
            this.saveAndNewAction &&
            this.getAcl().checkScope(this.entityType, 'create')
        ) {
            this.dropdownItemList.push({
                name: 'saveAndNew',
                label: 'Save & New',
                title: 'Ctrl+Alt+Enter',
            });
        }
    }

    /**
     * A `save-and-create-new` action.
     */
    actionSaveAndNew(data) {
        data = data || {};

        let proceedCallback = () => {
            Espo.Ui.success(this.translate('Created'));

            this.getRouter().dispatch(this.scope, 'create', {
                rootUrl: this.options.rootUrl,
                focusForCreate: !!data.focusForCreate,
            });

            this.getRouter().navigate('#' + this.scope + '/create', {trigger: false});
        };

        this.save(data.options)
            .then(proceedCallback)
            .catch(() => {});

        if (this.lastSaveCancelReason === 'notModified') {
             proceedCallback();
        }
    }

    /**
     * @protected
     * @param {JQueryKeyEventObject} e
     */
    handleShortcutKeyEscape(e) {
        if (this.buttonsDisabled) {
            return;
        }

        if (this.buttonList.findIndex(item => item.name === 'cancel' && !item.hidden) === -1) {
            return;
        }

        e.preventDefault();
        e.stopPropagation();

        let focusedFieldView = this.getFocusedFieldView();

        if (focusedFieldView) {
            this.model.set(focusedFieldView.fetch());
        }

        if (this.isChanged) {
            this.confirm(this.translate('confirmLeaveOutMessage', 'messages'))
                .then(() => this.actionCancel());

            return;
        }

        this.actionCancel();
    }

    /**
     * @protected
     * @param {JQueryKeyEventObject} e
     */
    handleShortcutKeyCtrlAltEnter(e) {
        if (this.buttonsDisabled) {
            return;
        }

        e.preventDefault();
        e.stopPropagation();

        if (!this.saveAndNewAction) {
            return;
        }

        if (!this.hasAvailableActionItem('saveAndNew')) {
            return;
        }

        this.actionSaveAndNew({focusForCreate: true});
    }
}

export default EditRecordView;
PK]F�\�,�,�views/record/base.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/record/base */

import View from 'view';
import ViewRecordHelper from 'view-record-helper';
import DynamicLogic from 'dynamic-logic';
import _ from 'underscore';
import $ from 'jquery';
import DefaultsPopulator from 'helpers/model/defaults-populator';

/**
 * A base record view. To be extended.
 */
class BaseRecordView extends View {

    /**
     * A type.
     */
    type = 'edit'

    /**
     * An entity type.
     *
     * @type {string|null}
     */
    entityType = null

    /**
     * A scope.
     *
     * @type {string|null}
     */
    scope = null

    /**
     * Is new. Is set automatically.
     */
    isNew = false

    /**
     * @deprecated
     * @protected
     */
    dependencyDefs = {}

    /**
     * Dynamic logic.
     *
     * @protected
     * @type {Object}
     */
    dynamicLogicDefs = {}

    /**
     * A field list.
     *
     * @protected
     */
    fieldList = null

    /**
     * A mode.
     *
     * @type {'detail'|'edit'|null}
     */
    mode = null

    /**
     * A last save cancel reason.
     *
     * @protected
     * @type {string|null}
     */
    lastSaveCancelReason = null

    /**
     * A record-helper.
     *
     * @protected
     * @type {module:view-record-helper}
     */
    recordHelper = null

    /** @const */
    MODE_DETAIL = 'detail'
    /** @const */
    MODE_EDIT = 'edit'

    /** @const */
    TYPE_DETAIL = 'detail'
    // noinspection JSUnusedGlobalSymbols
    /** @const  */
    TYPE_EDIT = 'edit'

    /**
     * Hide a field.
     *
     * @param {string} name A field name.
     * @param {boolean } [locked] To lock. Won't be able to un-hide.
     */
    hideField(name, locked) {
        this.recordHelper.setFieldStateParam(name, 'hidden', true);

        if (locked) {
            this.recordHelper.setFieldStateParam(name, 'hiddenLocked', true);
        }

        let processHtml = () => {
            let fieldView = this.getFieldView(name);

            if (fieldView) {
                let $field = fieldView.$el;
                let $cell = $field.closest('.cell[data-name="' + name + '"]');
                let $label = $cell.find('label.control-label[data-name="' + name + '"]');

                $field.addClass('hidden');
                $label.addClass('hidden');
                $cell.addClass('hidden-cell');
            }
            else {
                this.$el.find('.cell[data-name="' + name + '"]').addClass('hidden-cell');
                this.$el.find('.field[data-name="' + name + '"]').addClass('hidden');
                this.$el.find('label.control-label[data-name="' + name + '"]').addClass('hidden');
            }
        };

        if (this.isRendered()) {
            processHtml();
        }
        else {
            this.once('after:render', () => {
                processHtml();
            });
        }

        let view = this.getFieldView(name);

        if (view) {
            view.setDisabled(locked);
        }
    }

    /**
     * Show a field.
     *
     * @param {string} name A field name.
     */
    showField(name) {
        if (this.recordHelper.getFieldStateParam(name, 'hiddenLocked')) {
            return;
        }

        this.recordHelper.setFieldStateParam(name, 'hidden', false);

        let processHtml = () => {
            let fieldView = this.getFieldView(name);

            if (fieldView) {
                let $field = fieldView.$el;
                let $cell = $field.closest('.cell[data-name="' + name + '"]');
                let $label = $cell.find('label.control-label[data-name="' + name + '"]');

                $field.removeClass('hidden');
                $label.removeClass('hidden');
                $cell.removeClass('hidden-cell');

                return;
            }

            this.$el.find('.cell[data-name="' + name + '"]').removeClass('hidden-cell');
            this.$el.find('.field[data-name="' + name + '"]').removeClass('hidden');
            this.$el.find('label.control-label[data-name="' + name + '"]').removeClass('hidden');
        };

        if (this.isRendered()) {
            processHtml();
        }
        else {
            this.once('after:render', () => {
                processHtml();
            });
        }

        let view = this.getFieldView(name);

        if (view) {
            if (!view.disabledLocked) {
                view.setNotDisabled();
            }
        }
    }

    /**
     * Set a field as read-only.
     *
     * @param {string} name A field name.
     * @param {boolean } [locked] To lock. Won't be able to un-set.
     */
    setFieldReadOnly(name, locked) {
        const previousValue = this.recordHelper.getFieldStateParam(name, 'readOnly');

        this.recordHelper.setFieldStateParam(name, 'readOnly', true);

        if (locked) {
            this.recordHelper.setFieldStateParam(name, 'readOnlyLocked', true);
        }

        const view = this.getFieldView(name);

        if (view) {
            view.setReadOnly(locked);
        }

        if (!previousValue) {
            this.trigger('set-field-read-only', name);
        }

        /**
         * @todo
         *   Move to fields/base. Listen to recordHelper 'field-change' (if recordHelper is available).
         *   Same for set state methods.
         *   Issue is that sometimes state is changed in between view initialization (for bottom views with fields).
         */

        if (!view && !this.isReady) {
            this.once('ready', () => {
                const view = this.getFieldView(name);

                if (
                    view &&
                    !view.readOnly &&
                    this.recordHelper.getFieldStateParam(name, 'readOnly')
                ) {
                    view.setReadOnly(locked);
                }
            })
        }
    }

    /**
     * Set a field as not read-only.
     *
     * @param {string} name A field name.
     */
    setFieldNotReadOnly(name) {
        const previousValue = this.recordHelper.getFieldStateParam(name, 'readOnly');

        this.recordHelper.setFieldStateParam(name, 'readOnly', false);

        const view = this.getFieldView(name);

        if (view && view.readOnly) {
            view.setNotReadOnly();

            if (this.mode === this.MODE_EDIT) {
                if (!view.readOnlyLocked && view.isDetailMode()) {
                    view.setEditMode()
                        .then(() => view.reRender());
                }
            }
        }

        if (previousValue) {
            this.trigger('set-field-not-read-only', name);
        }

        if (!view && !this.isReady) {
            this.once('ready', () => {
                const view = this.getFieldView(name);

                if (
                    view &&
                    view.readOnly &&
                    !this.recordHelper.getFieldStateParam(name, 'readOnly')
                ) {
                    view.setNotReadOnly();
                }
            })
        }
    }

    /**
     * Set a field as required.
     *
     * @param {string} name A field name.
     */
    setFieldRequired(name) {
        const previousValue = this.recordHelper.getFieldStateParam(name, 'required');

        this.recordHelper.setFieldStateParam(name, 'required', true);

        const view = this.getFieldView(name);

        if (view) {
            view.setRequired();
        }

        if (!previousValue) {
            this.trigger('set-field-required', name);
        }
    }

    /**
     * Set a field as not required.
     *
     * @param {string} name A field name.
     */
    setFieldNotRequired(name) {
        const previousValue = this.recordHelper.getFieldStateParam(name, 'required');

        this.recordHelper.setFieldStateParam(name, 'required', false);

        const view = this.getFieldView(name);

        if (view) {
            view.setNotRequired();
        }

        if (previousValue) {
            this.trigger('set-field-not-required', name);
        }
    }

    /**
     * Set an option list for a field.
     *
     * @param {string} name A field name.
     * @param {string[]} list Options.
     */
    setFieldOptionList(name, list) {
        let had = this.recordHelper.hasFieldOptionList(name);
        let previousList = this.recordHelper.getFieldOptionList(name);

        this.recordHelper.setFieldOptionList(name, list);

        let view = this.getFieldView(name);

        if (view) {
            if ('setOptionList' in view) {
                view.setOptionList(list);
            }
        }

        if (!had || !_(previousList).isEqual(list)) {
            this.trigger('set-field-option-list', name, list);
        }
    }

    /**
     * Reset field options (revert to default).
     *
     * @param {string} name A field name.
     */
    resetFieldOptionList(name) {
        let had = this.recordHelper.hasFieldOptionList(name);

        this.recordHelper.clearFieldOptionList(name);

        let view = this.getFieldView(name);

        if (view) {
            if ('resetOptionList' in view) {
                view.resetOptionList();
            }
        }

        if (had) {
            this.trigger('reset-field-option-list', name);
        }
    }

    /**
     * Show a panel.
     *
     * @param {string} name A panel name.
     * @param [softLockedType] Omitted.
     */
    showPanel(name, softLockedType) {
        this.recordHelper.setPanelStateParam(name, 'hidden', false);

        if (this.isRendered()) {
            this.$el.find('.panel[data-name="'+name+'"]').removeClass('hidden');
        }
    }

    /**
     * Hide a panel.
     *
     * @param {string} name A panel name.
     * @param {boolean} [locked=false] Won't be able to un-hide.
     * @param {module:views/record/detail~panelSoftLockedType} [softLockedType='default']
     */
    hidePanel(name, locked, softLockedType) {
        this.recordHelper.setPanelStateParam(name, 'hidden', true);

        if (this.isRendered()) {
            this.$el.find('.panel[data-name="'+name+'"]').addClass('hidden');
        }
    }

    /**
     * Style a panel. Style is set in the `data-style` DOM attribute.
     *
     * @param {string} name A panel name.
     */
    stylePanel(name) {
        this.recordHelper.setPanelStateParam(name, 'styled', true);

        let process = () => {
            let $panel = this.$el.find('.panel[data-name="'+name+'"]');
            let $btn = $panel.find('> .panel-heading .btn');

            let style = $panel.attr('data-style');

            if (!style) {
                return;
            }

            $panel.removeClass('panel-default');
            $panel.addClass('panel-' + style);

            $btn.removeClass('btn-default');
            $btn.addClass('btn-' + style);
        };

        if (this.isRendered()) {
            process();

            return;
        }

        this.once('after:render', () => {
            process();
        });
    }

    /**
     * Un-style a panel.
     *
     * @param {string} name A panel name.
     */
    unstylePanel(name) {
        this.recordHelper.setPanelStateParam(name, 'styled', false);

        let process = () => {
            let $panel = this.$el.find('.panel[data-name="'+name+'"]');
            let $btn = $panel.find('> .panel-heading .btn');

            let style = $panel.attr('data-style');

            if (!style) {
                return;
            }

            $panel.removeClass('panel-' + style);
            $panel.addClass('panel-default');

            $btn.removeClass('btn-' + style);
            $btn.addClass('btn-default');
        };

        if (this.isRendered()) {
            process();

            return;
        }

        this.once('after:render', () => {
            process();
        });
    }

    /**
     * Set/unset a confirmation upon leaving the form.
     *
     * @param {boolean} value True sets a required confirmation.
     */
    setConfirmLeaveOut(value) {
        if (!this.getRouter()) {
            return;
        }

        this.getRouter().confirmLeaveOut = value;
    }

    /**
     * Get field views.
     *
     * @param {boolean} [withHidden] With hidden.
     * @return {Object.<string, module:views/fields/base>}
     */
    getFieldViews(withHidden) {
        let fields = {};

        this.fieldList.forEach(item => {
            let view = this.getFieldView(item);

            if (view) {
                fields[item] = view;
            }
        });

        return fields;
    }

    /**
     * @deprecated Use `getFieldViews`.
     * @private
     * @return {Object<string, module:views/fields/base>}
     */
    getFields() {
        return this.getFieldViews();
    }

    /**
     * Get a field view.
     *
     * @param {string} name A field name.
     * @return {module:views/fields/base|null}
     */
    getFieldView(name) {
        /** @type {module:views/fields/base|null} */
        let view =  this.getView(name + 'Field') || null;

        // @todo Remove.
        if (!view) {
            view = this.getView(name) || null;
        }

        return view;
    }

    /**
     * @deprecated Use `getFieldView`.
     * @return {module:views/fields/base|null}
     */
    getField(name) {
        return this.getFieldView(name);
    }

    /**
     * Get a field list.
     *
     * @return {string[]}
     */
    getFieldList() {
        return Object.keys(this.getFieldViews());
    }

    /**
     * Get a field view list.
     *
     * @return {module:views/fields/base[]}
     */
    getFieldViewList() {
        return this.getFieldList()
            .map(field => this.getFieldView(field))
            .filter(view => view !== null);
    }

    /**
     * @inheritDoc
     */
    data() {
        return {
            scope: this.scope,
            entityType: this.entityType,
            hiddenPanels: this.recordHelper.getHiddenPanels(),
            hiddenFields: this.recordHelper.getHiddenFields(),
        };
    }

    /**
     * @todo Remove.
     * @private
     */
    handleDataBeforeRender(data) {
        this.getFieldList().forEach((field) => {
            let viewKey = field + 'Field';

            data[field] = data[viewKey];
        });
    }

    /**
     * @inheritDoc
     */
    setup() {
        if (typeof this.model === 'undefined') {
            throw new Error('Model has not been injected into record view.');
        }

        /** @type {module:view-record-helper} */
        this.recordHelper = this.options.recordHelper || new ViewRecordHelper();

        this.dynamicLogicDefs = this.options.dynamicLogicDefs || this.dynamicLogicDefs;

        this.on('remove', () => {
            if (this.isChanged) {
                this.resetModelChanges();
            }

            this.setIsNotChanged();
        });

        this.entityType = this.model.entityType || this.model.name || 'Common';
        this.scope = this.options.scope || this.entityType;

        this.fieldList = this.options.fieldList || this.fieldList || [];

        this.numId = Math.floor((Math.random() * 10000) + 1);

        this.id = Espo.Utils.toDom(this.entityType) + '-' +
            Espo.Utils.toDom(this.type) + '-' + this.numId;

        if (this.model.isNew()) {
            this.isNew = true;
        }

        this.setupBeforeFinal();
    }

    /**
     * Set up before final.
     *
     * @protected
     */
    setupBeforeFinal() {
        this.attributes = this.model.getClonedAttributes();

        this.listenTo(this.model, 'change', (m, o) => {
            if (o.sync) {
                for (let attribute in m.attributes) {
                    if (!m.hasChanged(attribute)) {
                        continue;
                    }

                    this.attributes[attribute] = Espo.Utils.cloneDeep(
                        m.get(attribute)
                    );
                }

                return;
            }

            if (this.mode === this.MODE_EDIT) {
                this.setIsChanged();
            }
        });

        if (this.options.attributes) {
            this.model.set(this.options.attributes);
        }

        this.listenTo(this.model, 'sync', () => {
             this.attributes = this.model.getClonedAttributes();
        });

        this.initDependency();
        this.initDynamicLogic();
    }

    /**
     * Set an initial attribute value.
     *
     * @protected
     * @param {string} attribute An attribute name.
     * @param {*} value
     */
    setInitialAttributeValue(attribute, value) {
        this.attributes[attribute] = value;
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * Check whether a current attribute value differs from initial.
     *
     * @param {string} name An attribute name.
     * @return {boolean}
     */
    checkAttributeIsChanged(name) {
        return !_.isEqual(this.attributes[name], this.model.get(name));
    }

    /**
     * Reset model changes.
     */
    resetModelChanges() {
        if (this.updatedAttributes) {
            this.attributes = this.updatedAttributes;

            this.updatedAttributes = null;
        }

        let attributes = this.model.attributes;

        for (let attr in attributes) {
            if (!(attr in this.attributes)) {
                this.model.unset(attr);
            }
        }

        this.model.set(this.attributes, {skipReRender: true});
    }

    /**
     * Set model attribute values.
     *
     * @param {Object.<string,*>} setAttributes Values.
     * @param {Object.<string,*>} [options] Options.
     */
    setModelAttributes(setAttributes, options) {
        for (let item in this.model.attributes) {
            if (!(item in setAttributes)) {
                this.model.unset(item);
            }
        }

        this.model.set(setAttributes, options || {});
    }

    /**
     * Init dynamic logic.
     *
     * @protected
     */
    initDynamicLogic() {
        this.dynamicLogicDefs = Espo.Utils.clone(this.dynamicLogicDefs || {});
        this.dynamicLogicDefs.fields = Espo.Utils.clone(this.dynamicLogicDefs.fields);
        this.dynamicLogicDefs.panels = Espo.Utils.clone(this.dynamicLogicDefs.panels);

        this.dynamicLogic = new DynamicLogic(this.dynamicLogicDefs, this);

        this.listenTo(this.model, 'change', () => this.processDynamicLogic());
        this.processDynamicLogic();
    }

    /**
     * Process dynamic logic.
     *
     * @protected
     */
    processDynamicLogic() {
        this.dynamicLogic.process();
    }

    /**
     * @protected
     * @internal
     */
    initDependency() {
        // noinspection JSDeprecatedSymbols
        Object.keys(this.dependencyDefs || {}).forEach((attr) => {
            this.listenTo(this.model, 'change:' + attr, () => {
                this._handleDependencyAttribute(attr);
            });
        });

        this._handleDependencyAttributes();
    }

    /**
     * @deprecated
     * @private
     * For bc.
     */
    initDependancy() {
        this.initDependency();
    }

    /**
     * Set up a field level security.
     *
     * @protected
     */
    setupFieldLevelSecurity() {
        let forbiddenFieldList = this.getAcl().getScopeForbiddenFieldList(this.entityType, 'read');

        forbiddenFieldList.forEach((field) => {
            this.hideField(field, true);
        });

        let readOnlyFieldList = this.getAcl().getScopeForbiddenFieldList(this.entityType, 'edit');

        readOnlyFieldList.forEach((field) => {
            this.setFieldReadOnly(field, true);
        });
    }

    /**
     * Set is changed.
     *
     * @protected
     */
    setIsChanged() {
        this.isChanged = true;
    }

    /**
     * Set is not changed.
     *
     * @protected
     */
    setIsNotChanged() {
        this.isChanged = false;
    }

    /**
     * Validate.
     *
     * @return {boolean} True if not valid.
     */
    validate() {
        let invalidFieldList = [];

        this.getFieldList().forEach(field => {
            let fieldIsInvalid = this.validateField(field);

            if (fieldIsInvalid) {
                invalidFieldList.push(field)
            }
        });

        if (!!invalidFieldList.length) {
            this.onInvalid(invalidFieldList);
        }

        return !!invalidFieldList.length;
    }

    /**
     * @protected
     * @param {string[]} invalidFieldList Invalid fields.
     */
    onInvalid(invalidFieldList) {}

    /**
     * Validate a specific field.
     *
     * @param {string} field A field name.
     * @return {boolean} True if not valid.
     */
    validateField(field) {
        let fieldView = this.getFieldView(field);

        if (!fieldView) {
            return false;
        }

        let notValid = false;

        if (
            fieldView.isEditMode() &&
            !fieldView.disabled &&
            !fieldView.readOnly
        ) {
            notValid = fieldView.validate() || notValid;
        }

        if (notValid) {
            if (fieldView.$el) {
                let rect = fieldView.$el.get(0).getBoundingClientRect();

                if (
                    rect.top === 0 &&
                    rect.bottom === 0 &&
                    rect.left === 0 &&
                    fieldView.$el.closest('.panel.hidden').length
                ) {
                    setTimeout(() => {
                        let msg = this.translate('Not valid') + ': ' +
                            (
                                fieldView.lastValidationMessage ||
                                this.translate(field, 'fields', this.entityType)
                            );

                        Espo.Ui.error(msg, true);
                    }, 10);
                }
            }

            return true;
        }

        if (
            this.dynamicLogic &&
            this.dynamicLogicDefs &&
            this.dynamicLogicDefs.fields &&
            this.dynamicLogicDefs.fields[field] &&
            this.dynamicLogicDefs.fields[field].invalid &&
            this.dynamicLogicDefs.fields[field].invalid.conditionGroup
        ) {
            let invalidConditionGroup = this.dynamicLogicDefs.fields[field].invalid.conditionGroup;

            let fieldInvalid = this.dynamicLogic.checkConditionGroup(invalidConditionGroup);

            notValid = fieldInvalid || notValid;

            if (fieldInvalid) {
                let msg =
                    this.translate('fieldInvalid', 'messages')
                        .replace('{field}', this.translate(field, 'fields', this.entityType));

                fieldView.showValidationMessage(msg);

                fieldView.trigger('invalid');
            }
        }

        return notValid;
    }

    /**
     * Processed after save.
     */
    afterSave() {
        if (this.isNew) {
            Espo.Ui.success(this.translate('Created'));
        }
        else {
            Espo.Ui.success(this.translate('Saved'));
        }

        this.setIsNotChanged();
    }

    /**
     * Processed before before-save.
     */
    beforeBeforeSave() {}

    /**
     * Processed before save.
     */
    beforeSave() {
        Espo.Ui.notify(this.translate('saving', 'messages'));
    }

    /**
     * Processed after save error.
     */
    afterSaveError() {}

    /**
     * Processed after save a not modified record.
     */
    afterNotModified() {
        Espo.Ui.warning(this.translate('notModified', 'messages'));

        this.setIsNotChanged();
    }

    /**
     * Processed after save not valid.
     */
    afterNotValid() {
        Espo.Ui.error(this.translate('Not valid'));
    }

    /**
     * Save options.
     *
     * @typedef {Object} module:views/record/base~saveOptions
     *
     * @property {Object.<string,string>} [headers] HTTP headers.
     * @property {boolean} [skipNotModifiedWarning] Don't show a not-modified warning.
     * @property {function():void} [afterValidate] A callback called after validate.
     * @property {boolean} [bypassClose] Bypass closing. Only for inline-edit.
     */

    /**
     * Save.
     *
     * @param {module:views/record/base~saveOptions} [options] Options.
     * @return {Promise}
     */
    save(options) {
        options = options || {};

        let headers = options.headers || {};

        let model = this.model;

        this.lastSaveCancelReason = null;

        this.beforeBeforeSave();

        let fetchedAttributes = this.fetch();
        let initialAttributes = this.attributes;
        let beforeSaveAttributes = this.model.getClonedAttributes();

        let attributes = _.extend(
            Espo.Utils.cloneDeep(beforeSaveAttributes),
            fetchedAttributes
        );

        let setAttributes = {};

        if (model.isNew()) {
            setAttributes = attributes;
        }

        if (!model.isNew()) {
            for (let attr in attributes) {
                if (_.isEqual(initialAttributes[attr], attributes[attr])) {
                    continue;
                }

                setAttributes[attr] = attributes[attr];
            }

            let forcePatchAttributeDependencyMap = this.forcePatchAttributeDependencyMap || {};

            for (let attr in forcePatchAttributeDependencyMap) {
                if (attr in setAttributes) {
                    continue;
                }

                if (!(attr in fetchedAttributes)) {
                    continue;
                }

                let depAttributeList = forcePatchAttributeDependencyMap[attr];

                let treatAsChanged = !! depAttributeList.find(attr => attr in setAttributes);

                if (treatAsChanged) {
                    setAttributes[attr] = attributes[attr];
                }
            }
        }

        if (Object.keys(setAttributes).length === 0) {
            if (!options.skipNotModifiedWarning) {
                this.afterNotModified();
            }

            this.lastSaveCancelReason = 'notModified';

            this.trigger('cancel:save', {reason: 'notModified'});

            return Promise.reject('notModified');
        }

        model.set(setAttributes, {silent: true});

        if (this.validate()) {
            model.attributes = beforeSaveAttributes;

            this.afterNotValid();

            this.lastSaveCancelReason = 'invalid';

            this.trigger('cancel:save', {reason: 'invalid'});

            return Promise.reject('invalid');
        }

        if (options.afterValidate) {
            options.afterValidate();
        }

        let optimisticConcurrencyControl = this.getMetadata()
            .get(['entityDefs', this.entityType, 'optimisticConcurrencyControl']);

        if (optimisticConcurrencyControl && this.model.get('versionNumber') !== null) {
            headers['X-Version-Number'] = this.model.get('versionNumber');
        }

        if (this.model.isNew() && this.options.duplicateSourceId) {
            headers['X-Duplicate-Source-Id'] = this.options.duplicateSourceId;
        }

        this.beforeSave();

        this.trigger('before:save');
        model.trigger('before:save');

        let onError = (xhr, reject, resolve) => {
            this.handleSaveError(xhr, options, resolve)
                .then(skipReject => {
                    if (skipReject) {
                        return;
                    }

                    reject('error');
                });

            this.afterSaveError();
            this.setModelAttributes(beforeSaveAttributes);

            this.lastSaveCancelReason = 'error';

            this.trigger('error:save');
            this.trigger('cancel:save', {reason: 'error'});
        };

        return new Promise((resolve, reject) => {
            model
                .save(
                    setAttributes,
                    {
                        patch: !model.isNew(),
                        headers: headers,
                    },
                )
                .then(() => {
                    this.trigger('save', initialAttributes);

                    this.afterSave();

                    if (this.isNew) {
                        this.isNew = false;
                    }

                    this.trigger('after:save');
                    model.trigger('after:save');

                    resolve();
                })
                .catch(xhr => {
                    onError(xhr, reject, resolve);
                });
        });
    }

    /**
     * Handle a save error.
     *
     * @param {module:ajax.Xhr} xhr XHR.
     * @param {module:views/record/base~saveOptions} [options] Options.
     * @param {function} saveResolve Resolve save promise.
     * @return {Promise<boolean>}
     */
    handleSaveError(xhr, options, saveResolve) {
        let handlerData = null;

        if (~[409, 500].indexOf(xhr.status)) {
            let statusReason = xhr.getResponseHeader('X-Status-Reason');

            if (!statusReason) {
                return Promise.resolve(false);
            }

            try {
                handlerData = JSON.parse(statusReason);
            }
            catch (e) {}

            if (!handlerData) {
                handlerData = {
                    reason: statusReason.toString(),
                };

                if (xhr.responseText) {
                    let data;

                    try {
                        data = JSON.parse(xhr.responseText);
                    }
                    catch (e) {
                        console.error('Could not parse error response body.');

                        return Promise.resolve(false);
                    }

                    handlerData.data = data;
                }
            }
        }

        if (!handlerData || !handlerData.reason) {
            return Promise.resolve(false);
        }

        let reason = handlerData.reason;

        let handlerName =
            this.getMetadata()
                .get(['clientDefs', this.scope, 'saveErrorHandlers', reason]) ||
            this.getMetadata()
                .get(['clientDefs', 'Global', 'saveErrorHandlers', reason]);

        return new Promise(resolve => {
            if (handlerName) {
                Espo.loader.require(handlerName, Handler => {
                    let handler = new Handler(this);

                    handler.process(handlerData.data, options);

                    resolve(false);
                });

                xhr.errorIsHandled = true;

                return;
            }

            let methodName = 'errorHandler' + Espo.Utils.upperCaseFirst(reason);

            if (methodName in this) {
                xhr.errorIsHandled = true;

                let skipReject = this[methodName](handlerData.data, options, saveResolve);

                resolve(skipReject || false);

                return;
            }

            resolve(false);
        });
    }

    /**
     * Fetch data from the form.
     *
     * @return {Object.<string,*>}
     */
    fetch() {
        let data = {};
        let fieldViews = this.getFieldViews();

        for (let i in fieldViews) {
            let view = fieldViews[i];

            if (!view.isEditMode()) {
                continue;
            }

            if (!view.disabled && !view.readOnly && view.isFullyRendered()) {
                data = {...data, ...view.fetch()};
            }
        }

        return data;
    }

    /**
     * Process fetch.
     *
     * @return {Object<string,*>|null}
     */
    processFetch() {
        let data = this.fetch();

        this.model.set(data);

        if (this.validate()) {
            return null;
        }

        return data;
    }

    /**
     * Populate defaults.
     */
    populateDefaults() {
        const populator = new DefaultsPopulator(
            this.getUser(),
            this.getPreferences(),
            this.getAcl(),
            this.getConfig()
        );

        populator.populate(this.model);
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * @protected
     * @param duplicates
     */
    errorHandlerDuplicate(duplicates) {}

    /**
     * @private
     */
    _handleDependencyAttributes() {
        // noinspection JSDeprecatedSymbols
        Object.keys(this.dependencyDefs || {}).forEach(attr => {
            this._handleDependencyAttribute(attr);
        });
    }

    /**
     * @private
     */
    _handleDependencyAttribute(attr) {
        // noinspection JSDeprecatedSymbols
        let data = this.dependencyDefs[attr];
        let value = this.model.get(attr);

        if (value in (data.map || {})) {
            (data.map[value] || []).forEach((item) => {
                this._doDependencyAction(item);
            });

            return;
        }

        if ('default' in data) {
            (data.default || []).forEach((item) => {
                this._doDependencyAction(item);
            });
        }
    }

    /**
     * @private
     */
    _doDependencyAction(data) {
        let action = data.action;

        let methodName = 'dependencyAction' + Espo.Utils.upperCaseFirst(action);

        if (methodName in this && typeof this.methodName === 'function') {
            this.methodName(data);

            return;
        }

        let fieldList = data.fieldList || data.fields || [];
        let panelList = data.panelList || data.panels || [];

        switch (action) {
            case 'hide':
                panelList.forEach((item) => {
                    this.hidePanel(item);
                });

                fieldList.forEach((item) => {
                    this.hideField(item);
                });

                break;

            case 'show':
                panelList.forEach((item) => {
                    this.showPanel(item);
                });

                fieldList.forEach((item) => {
                    this.showField(item);
                });

                break;

            case 'setRequired':
                fieldList.forEach((field) => {
                    this.setFieldRequired(field);
                });

                break;

            case 'setNotRequired':
                fieldList.forEach((field) => {
                    this.setFieldNotRequired(field);
                });

                break;

            case 'setReadOnly':
                fieldList.forEach((field) => {
                    this.setFieldReadOnly(field);
                });

                break;

            case 'setNotReadOnly':
                fieldList.forEach((field) => {
                    this.setFieldNotReadOnly(field);
                });

                break;
        }
    }

    /**
     * Create a field view.
     *
     * @protected
     * @param {string} name A field name.
     * @param {string|null} [view] A view name/path.
     * @param {Object<string,*>} [params] Field params.
     * @param {'detail'|'edit'} [mode='edit'] A mode.
     * @param {boolean} [readOnly] Read-only.
     * @param {Object<string,*>} [options] View options.
     */
    createField(name, view, params, mode, readOnly, options) {
        let o = {
            model: this.model,
            mode: mode || 'edit',
            selector: '.field[data-name="' + name + '"]',
            defs: {
                name: name,
                params: params || {},
            },
        };

        if (readOnly) {
            o.readOnly = true;
        }

        view = view || this.model.getFieldParam(name, 'view');

        if (!view) {
            let type = this.model.getFieldType(name) || 'base';
            view = this.getFieldManager().getViewName(type);
        }

        if (options) {
            for (let param in options) {
                o[param] = options[param];
            }
        }

        if (this.recordHelper.getFieldStateParam(name, 'hidden')) {
            o.disabled = true;
        }

        if (this.recordHelper.getFieldStateParam(name, 'readOnly')) {
            o.readOnly = true;
        }

        if (this.recordHelper.getFieldStateParam(name, 'required') !== null) {
            o.defs.params.required = this.recordHelper.getFieldStateParam(name, 'required');
        }

        if (this.recordHelper.hasFieldOptionList(name)) {
            o.customOptionList = this.recordHelper.getFieldOptionList(name);
        }

        let viewKey = name + 'Field';

        this.createView(viewKey, view, o);

        if (!~this.fieldList.indexOf(name)) {
            this.fieldList.push(name);
        }
    }

    /**
     * Get a currently focused field view.
     *
     * @return {module:views/fields/base|null}
     */
    getFocusedFieldView() {
        let $active = $(window.document.activeElement);

        if (!$active.length) {
            return null;
        }

        let $field = $active.closest('.field');

        if (!$field.length) {
            return null;
        }

        let name = $field.attr('data-name');

        if (!name) {
            return null;
        }

        return this.getFieldView(name);
    }

    /**
     * Process exit.
     *
     * @param {string} [after] An exit parameter.
     */
    exit(after) {}
}

export default BaseRecordView;
PK]	6y3#views/record/deleted-detail-side.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/record/deleted-detail-side', ['views/record/detail-side'], function (Dep) {

    return Dep.extend({

        additionalPanelsDisabled: true,

    });
});
PK]�������views/record/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/record/detail */

import BaseRecordView from 'views/record/base';
import ViewRecordHelper from 'view-record-helper';
import ActionItemSetup from 'helpers/action-item-setup';

/**
 * A detail record view.
 */
class DetailRecordView extends BaseRecordView {

    /**
     * @typedef {Object} module:views/record/detail~options
     *
     * @property {module:model} model A model.
     * @property {string} [scope] A scope.
     * @property {string} [layoutName] A layout name.
     * @property {module:views/record/detail~panelDefs[]} [detailLayout] A detail layout.
     * @property {boolean} [readOnly] Read-only.
     * @property {string} [rootUrl]
     * @property {string} [returnUrl]
     * @property {boolean} [returnAfterCreate]
     * @property {boolean} [editModeDisabled]
     * @property {boolean} [confirmLeaveDisabled]
     * @property {boolean} [editModeDisabled]
     * @property {boolean} [isWide]
     * @property {string} [sideView]
     * @property {string} [bottomView]
     * @property {string} [inlineEditDisabled] Disable inline edit.
     * @property {string} [navigateButtonsDisabled]
     * @property {Object} [dynamicLogicDefs]
     * @property {module:view-record-helper} [recordHelper] A record helper. For a form state management.
     * @property {Object.<string, *>} [attributes]
     * @property {module:views/record/detail~button[]} [buttonList] Buttons.
     * @property {module:views/record/detail~dropdownItem[]} [dropdownItemList] Dropdown items.
     * @property {Object.<string, *>} [dataObject] Additional data.
     */

    /**
     * @param {module:views/record/detail~options | Object.<string, *>} options Options.
     */
    constructor(options) {
        super(options);
    }

    /** @inheritDoc */
    template = 'record/detail'

    /** @inheritDoc */
    type = 'detail'

    /**
     * A layout name. Can be overridden by an option parameter.
     *
     * @protected
     * @type {string}
     */
    layoutName = 'detail'

    /**
     * Panel definitions.
     *
     * @typedef {Object} module:views/record/detail~panelDefs
     * @property {string} [label] A translatable label.
     * @property {string} [customLabel] A custom label.
     * @property {string} [name] A name. Useful to be able to show/hide by a name.
     * @property {'default'|'success'|'danger'|'warning'} [style] A style.
     * @property {boolean} [tabBreak] Is a tab-break.
     * @property {string} [tabLabel] A tab label. If starts with `$`, a translation
     *   of the `tabs` category is used.
     * @property {module:views/record/detail~rowDefs[]} [rows] Rows.
     * @property {module:views/record/detail~rowDefs[]} [columns] Columns.
     */

    /**
     * A row.
     *
     * @typedef {Array<module:views/record/detail~cellDefs|false>} module:views/record/detail~rowDefs
     */

    /**
     * Cell definitions.
     *
     * @typedef {Object} module:views/record/detail~cellDefs
     * @property {string} [name] A name (usually a field name).
     * @property {string|module:views/fields/base} [view] An overridden field view name or a view instance.
     * @property {string} [type] An overridden field type.
     * @property {boolean} [readOnly] Read-only.
     * @property {boolean} [inlineEditDisabled] Disable inline edit.
     * @property {Object.<string, *>} [params] Overridden field parameters.
     * @property {Object.<string, *>} [options] Field view options.
     * @property {string} [labelText] A label text (not-translatable).
     * @property {boolean} [noLabel] No label.
     * @property {string} [label] A translatable label (using the `fields` category).
     * @property {1|2|3|4} [span] A width.
     */

    /**
     * A layout. If null, then will be loaded from the backend (using the `layoutName` value).
     * Can be overridden by an option parameter.
     *
     * @protected
     * @type {module:views/record/detail~panelDefs[]|null}
     */
    detailLayout = null

    /**
     * A fields mode.
     *
     * @protected
     * @type {'detail'|'edit'|'list'}
     */
    fieldsMode = 'detail'

    /**
     * A current mode. Only for reading.
     *
     * @protected
     * @type {'detail'|'edit'}
     */
    mode = 'detail'

    /**
     * @private
     */
    gridLayout = null

    /**
     * Disable buttons. Can be overridden by an option parameter.
     *
     * @protected
     * @type {boolean}
     */
    buttonsDisabled = false

    /**
     * Is record new. Only for reading.
     *
     * @protected
     */
    isNew = false

    /**
     * A button. Handled by an `action{Name}` method, a click handler or a handler class.
     *
     * @typedef module:views/record/detail~button
     *
     * @property {string} name A name.
     * @property {string} [label] A label.
     * @property {string} [labelTranslation] A label translation path.
     * @property {string} [html] An HTML.
     * @property {string} [text] A text.
     * @property {'default'|'danger'|'success'|'warning'} [style] A style.
     * @property {boolean} [hidden] Hidden.
     * @property {string} [title] A title (not translatable).
     * @property {boolean} [disabled] Disabled.
     * @property {function()} [onClick] A click handler.
     */

    /**
     * A dropdown item. Handled by an `action{Name}` method, a click handler or a handler class.
     *
     * @typedef module:views/record/detail~dropdownItem
     *
     * @property {string} name A name.
     * @property {string} [label] A label.
     * @property {string} [labelTranslation] A label translation path.
     * @property {string} [html] An HTML.
     * @property {string} [text] A text.
     * @property {boolean} [hidden] Hidden.
     * @property {Object.<string, string>} [data] Data attributes.
     * @property {string} [title] A title (not translatable).
     * @property {boolean} [disabled] Disabled.
     * @property {function()} [onClick] A click handler.
     */

    /**
     * A button list.
     *
     * @protected
     * @type {module:views/record/detail~button[]}
     */
    buttonList = [
        {
            name: 'edit',
            label: 'Edit',
            title: 'Ctrl+Space',
        },
    ]

    /**
     * A dropdown item list.
     *
     * @protected
     * @type {Array<module:views/record/detail~dropdownItem|false>}
     */
    dropdownItemList = [
        {
            name: 'delete',
            label: 'Remove',
        },
    ]

    /**
     * A button list for edit mode.
     *
     * @protected
     * @type {module:views/record/detail~button[]}
     */
    buttonEditList = [
        {
            name: 'save',
            label: 'Save',
            style: 'primary',
            edit: true,
            title: 'Ctrl+Enter',
        },
        {
            name: 'cancelEdit',
            label: 'Cancel',
            edit: true,
            title: 'Esc',
        },
    ]

    /**
     * A dropdown item list for edit mode.
     *
     * @protected
     * @type {module:views/record/detail~dropdownItem[]}
     */
    dropdownEditItemList = []

    /**
     * All action items disabled;
     *
     * @protected
     */
    allActionItemsDisabled = false

    /**
     * A DOM element ID. Only for reading.
     *
     * @private
     * @type {string|null}
     */
    id = null

    /**
     * A return-URL. Can be overridden by an option parameter.
     *
     * @protected
     * @type {string|null}
     */
    returnUrl = null

    /**
     * A return dispatch params. Can be overridden by an option parameter.
     *
     * @protected
     * @type {Object|null}
     */
    returnDispatchParams = null

    /**
     * A middle view name.
     *
     * @protected
     */
    middleView = 'views/record/detail-middle'

    /**
     * A side view name.
     *
     * @protected
     */
    sideView = 'views/record/detail-side'

    /**
     * A bottom view name.
     *
     * @protected
     */
    bottomView = 'views/record/detail-bottom'

    /**
     * Disable a side view. Can be overridden by an option parameter.
     *
     * @protected
     */
    sideDisabled = false

    /**
     * Disable a bottom view. Can be overridden by an option parameter.
     *
     * @protected
     */
    bottomDisabled = false

    /**
     * @protected
     */
    gridLayoutType = 'record'

    /**
     * Disable edit mode. Can be overridden by an option parameter.
     *
     * @protected
     */
    editModeDisabled = false

    /**
     * Disable navigate (prev, next) buttons. Can be overridden by an option parameter.
     *
     * @protected
     */
    navigateButtonsDisabled = false

    /**
     * Read-only. Can be overridden by an option parameter.
     */
    readOnly = false

    /**
     * Middle view expanded to full width (no side view).
     * Can be overridden by an option parameter.
     *
     * @protected
     */
    isWide = false

    /**
     * Enable a duplicate action.
     *
     * @protected
     */
    duplicateAction = true

    /**
     * Enable a self-assign action.
     *
     * @protected
     */
    selfAssignAction = false

    /**
     * Enable a print-pdf action.
     *
     * @protected
     */
    printPdfAction = true

    /**
     * Enable a convert-currency action.
     *
     * @protected
     */
    convertCurrencyAction = true

    /**
     * Enable a save-and-continue-editing action.
     *
     * @protected
     */
    saveAndContinueEditingAction = true

    /**
     * Disable the inline-edit. Can be overridden by an option parameter.
     *
     * @protected
     */
    inlineEditDisabled = false

    /**
     * Disable a portal layout usage. Can be overridden by an option parameter.
     *
     * @protected
     */
    portalLayoutDisabled = false

    /**
     * A panel soft-locked type.
     *
     * @typedef {'default'|'acl'|'delimiter'|'dynamicLogic'
     * } module:views/record/detail~panelSoftLockedType
     */

    /**
     * @private
     * @type {module:views/record/detail~panelSoftLockedType[]}
     */
    panelSoftLockedTypeList = [
        'default',
        'acl',
        'delimiter',
        'dynamicLogic',
    ]

    /**
     * Dynamic logic. Can be overridden by an option parameter.
     *
     * @protected
     * @type {Object}
     * @todo Add typedef.
     */
    dynamicLogicDefs = {}

    /**
     * Disable confirm leave-out processing.
     *
     * @protected
     */
    confirmLeaveDisabled = false

    /**
     * @protected
     */
    setupHandlerType = 'record/detail'

    /**
     * @protected
     */
    currentTab = 0

    /**
     * @protected
     * @type {Object.<string,*>|null}
     */
    middlePanelDefs = null

    /**
     * @protected
     * @type {Object.<string,*>[]|null}
     */
    middlePanelDefsList = null

    /**
     * @protected
     * @type {JQuery|null}
     */
    $middle = null

    /**
     * @protected
     * @type {JQuery|null}
     */
    $bottom = null

    /**
     * @private
     * @type {JQuery|null}
     */
    $detailButtonContainer = null

    /** @private */
    blockUpdateWebSocketPeriod = 500

    /**
     * @internal
     * @protected
     */
    stickButtonsFormBottomSelector

    /**
     * @protected
     * @type {string}
     */
    dynamicHandlerClassName

    /**
     * Disable access control.
     *
     * @protected
     * @type {boolean}
     */
    accessControlDisabled

    /**
     * @protected
     * @type {boolean}
     */
    inlineEditModeIsOn = false

    /**
     * A Ctrl+Enter shortcut action.
     *
     * @protected
     * @type {?string}
     */
    shortcutKeyCtrlEnterAction = 'save'

    /**
     * A shortcut-key => action map.
     *
     * @protected
     * @type {?Object.<string, string|function (JQueryKeyEventObject): void>}
     */
    shortcutKeys = {
        /** @this DetailRecordView */
        'Control+Enter': function (e) {
            this.handleShortcutKeyCtrlEnter(e);
        },
        /** @this DetailRecordView */
        'Control+Alt+Enter': function (e) {
            this.handleShortcutKeyCtrlAltEnter(e);
        },
        /** @this DetailRecordView */
        'Control+KeyS': function (e) {
            this.handleShortcutKeyCtrlS(e);
        },
        /** @this DetailRecordView */
        'Control+Space': function (e) {
            this.handleShortcutKeyCtrlSpace(e);
        },
        /** @this DetailRecordView */
        'Escape': function (e) {
            this.handleShortcutKeyEscape(e);
        },
        /** @this DetailRecordView */
        'Control+Backslash': function (e) {
            this.handleShortcutKeyControlBackslash(e);
        },
        /** @this DetailRecordView */
        'Control+ArrowLeft': function (e) {
            this.handleShortcutKeyControlArrowLeft(e);
        },
        /** @this DetailRecordView */
        'Control+ArrowRight': function (e) {
            this.handleShortcutKeyControlArrowRight(e);
        },
    }

    /**
     * @inheritDoc
     */
    events = {
        /** @this DetailRecordView */
        'click .button-container .action': function (e) {
            const target = /** @type {HTMLElement} */e.currentTarget;

            let actionItems = undefined;

            if (target.classList.contains('detail-action-item')) {
                actionItems = [...this.buttonList, ...this.dropdownItemList]
            }
            else if (target.classList.contains('edit-action-item')) {
                actionItems = [...this.buttonEditList, ...this.dropdownEditItemList];
            }

            Espo.Utils.handleAction(this, e.originalEvent, target, {actionItems: actionItems});
        },
        /** @this DetailRecordView */
        'click [data-action="showMoreDetailPanels"]': function () {
            this.showMoreDetailPanels();
        },
        /** @this DetailRecordView */
        'click .middle-tabs > button': function (e) {
            let tab = parseInt($(e.currentTarget).attr('data-tab'));

            this.selectTab(tab);
        },
    }

    /**
     * An `edit` action.
     */
    actionEdit() {
        if (!this.editModeDisabled) {
            this.setEditMode();

            this.focusOnFirstDiv();
            $(window).scrollTop(0);

            return;
        }

        let options = {
            id: this.model.id,
            model: this.model,
        };

        if (this.options.rootUrl) {
            options.rootUrl = this.options.rootUrl;
        }

        this.getRouter().navigate('#' + this.scope + '/edit/' + this.model.id, {trigger: false});
        this.getRouter().dispatch(this.scope, 'edit', options);
    }

    // noinspection JSUnusedGlobalSymbols
    actionDelete() {
        this.delete();
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * A `save` action.
     *
     * @param {{options?: module:views/record/base~saveOptions}} [data] Data.
     * @return Promise
     */
    actionSave(data) {
        data = data || {};

        let modeBeforeSave = this.mode;

        const promise = this.save(data.options)
            .catch(reason => {
                if (modeBeforeSave === this.MODE_EDIT && reason === 'error') {
                    this.setEditMode();
                }

                return Promise.reject(reason);
            });

        if (!this.lastSaveCancelReason || this.lastSaveCancelReason === 'notModified') {
            this.setDetailMode();

            this.focusOnFirstDiv();
            $(window).scrollTop(0);
        }

        return promise;
    }

    actionCancelEdit() {
        this.cancelEdit();

        this.focusOnFirstDiv();
        $(window).scrollTop(0);
    }

    focusOnFirstDiv() {
        let element = /** @type {HTMLElement} */this.$el.find('> div').get(0);

        if (element) {
            element.focus({preventScroll: true});
        }
    }

    /**
     * A `save-and-continue-editing` action.
     */
    actionSaveAndContinueEditing(data) {
        data = data || {};

        this.save(data.options)
            .catch(() => {});
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * A `self-assign` action.
     */
    actionSelfAssign() {
        let attributes = {
            assignedUserId: this.getUser().id,
            assignedUserName: this.getUser().get('name'),
        };

        if ('getSelfAssignAttributes' in this) {
            let attributesAdditional = this.getSelfAssignAttributes();

            if (attributesAdditional) {
                for (let i in attributesAdditional) {
                    attributes[i] = attributesAdditional[i];
                }
            }
        }

        this.model
            .save(attributes, {patch: true})
            .then(() => {
                Espo.Ui.success(this.translate('Self-Assigned'));
            });
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * A `convert-currency` action.
     */
    actionConvertCurrency() {
        this.createView('modalConvertCurrency', 'views/modals/convert-currency', {
            entityType: this.entityType,
            model: this.model,
        }, view => {
            view.render();

            this.listenToOnce(view, 'after:update', attributes => {
                let isChanged = false;

                for (let a in attributes) {
                    if (attributes[a] !== this.model.get(a)) {
                        isChanged = true;

                        break;
                    }
                }

                if (!isChanged) {
                    Espo.Ui.warning(this.translate('notUpdated', 'messages'));

                    return;
                }

                this.model
                    .fetch()
                    .then(() => {
                        Espo.Ui.success(this.translate('done', 'messages'));
                    });
            });
        });
    }

    /**
     * Compose attribute values for a self-assignment.
     *
     * @protected
     * @return {Object.<string,*>|null}
     */
    getSelfAssignAttributes() {
        return null;
    }

    /**
     * Set up action items.
     *
     * @protected
     */
    setupActionItems() {
        if (this.model.isNew()) {
            this.isNew = true;

            this.removeActionItem('delete');
        }
        else if (this.getMetadata().get(['clientDefs', this.scope, 'removeDisabled'])) {
            this.removeActionItem('delete');
        }

        if (this.duplicateAction) {
            if (
                this.getAcl().check(this.entityType, 'create') &&
                !this.getMetadata().get(['clientDefs', this.scope, 'duplicateDisabled'])
            ) {
                this.addDropdownItem({
                    'label': 'Duplicate',
                    'name': 'duplicate',
                });
            }
        }

        if (this.selfAssignAction) {
            if (
                this.getAcl().check(this.entityType, 'edit') &&
                !~this.getAcl().getScopeForbiddenFieldList(this.entityType).indexOf('assignedUser') &&
                !this.getUser().isPortal()
            ) {
                if (this.model.has('assignedUserId')) {
                    this.dropdownItemList.push({
                        'label': 'Self-Assign',
                        'name': 'selfAssign',
                        'hidden': !!this.model.get('assignedUserId')
                    });

                    this.listenTo(this.model, 'change:assignedUserId', () => {
                        if (!this.model.get('assignedUserId')) {
                            this.showActionItem('selfAssign');
                        }
                        else {
                            this.hideActionItem('selfAssign');
                        }
                    });
                }
            }
        }

        if (this.type === this.TYPE_DETAIL && this.printPdfAction) {
            let printPdfAction = true;

            if (
                !~(this.getHelper().getAppParam('templateEntityTypeList') || [])
                    .indexOf(this.entityType)
            ) {
                printPdfAction = false;
            }

            if (printPdfAction) {
                this.dropdownItemList.push({
                    'label': 'Print to PDF',
                    'name': 'printPdf',
                });
            }
        }

        if (this.type === this.TYPE_DETAIL && this.convertCurrencyAction) {
            if (
                this.getAcl().check(this.entityType, 'edit') &&
                !this.getMetadata().get(['clientDefs', this.scope, 'convertCurrencyDisabled'])
            ) {
                let currencyFieldList = this.getFieldManager()
                    .getEntityTypeFieldList(this.entityType, {
                        type: 'currency',
                        acl: 'edit',
                    });

                if (currencyFieldList.length) {
                    this.addDropdownItem({
                        label: 'Convert Currency',
                        name: 'convertCurrency',
                    });
                }
            }
        }

        if (
            this.type === this.TYPE_DETAIL &&
            this.getMetadata().get(['scopes', this.scope, 'hasPersonalData'])
        ) {
            if (this.getAcl().getPermissionLevel('dataPrivacyPermission') === 'yes') {
                this.dropdownItemList.push({
                    'label': 'View Personal Data',
                    'name': 'viewPersonalData'
                });
            }
        }

        if (this.type === this.TYPE_DETAIL && this.getMetadata().get(['scopes', this.scope, 'stream'])) {
            this.addDropdownItem({
                label: 'View Followers',
                name: 'viewFollowers'
            });
        }

        if (this.type === this.TYPE_DETAIL) {
            let actionItemSetup = new ActionItemSetup(
                this.getMetadata(),
                this.getHelper(),
                this.getAcl(),
                this.getLanguage()
            );

            actionItemSetup.setup(
                this,
                this.type,
                promise => this.wait(promise),
                item => this.addDropdownItem(item),
                name => this.showActionItem(name),
                name => this.hideActionItem(name)
            );

            if (this.saveAndContinueEditingAction) {
                this.dropdownEditItemList.push({
                    name: 'saveAndContinueEditing',
                    label: 'Save & Continue Editing',
                    title: 'Ctrl+S',
                });
            }
        }
    }

    /**
     * Disable action items.
     */
    disableActionItems() {
        // noinspection JSDeprecatedSymbols
        this.disableButtons();
    }

    /**
     * Enable action items.
     */
    enableActionItems() {
        // noinspection JSDeprecatedSymbols
        this.enableButtons();
    }

    /**
     * Hide a button or dropdown action item.
     *
     * @param {string} name A name.
     */
    hideActionItem(name) {
        for (let item of this.buttonList) {
            if (item.name === name) {
                item.hidden = true;

                break;
            }
        }

        for (let item of this.dropdownItemList) {
            if (item.name === name) {
                item.hidden = true;

                break;
            }
        }

        for (let item of this.dropdownEditItemList) {
            if (item.name === name) {
                item.hidden = true;

                break;
            }
        }

        for (let item of this.buttonEditList) {
            if (item.name === name) {
                item.hidden = true;

                break;
            }
        }

        if (this.isRendered()) {
            this.$detailButtonContainer
                .find('li > .action[data-action="'+name+'"]')
                .parent()
                .addClass('hidden');

            this.$detailButtonContainer
                .find('button.action[data-action="'+name+'"]')
                .addClass('hidden');

            if (this.isDropdownItemListEmpty()) {
                this.$dropdownItemListButton.addClass('hidden');
            }

            if (this.isDropdownEditItemListEmpty()) {
                this.$dropdownEditItemListButton.addClass('hidden');
            }

            this.adjustButtons();
        }
    }

    /**
     * Show a button or dropdown action item.
     *
     * @param {string} name A name.
     */
    showActionItem(name) {
        for (let item of this.buttonList) {
            if (item.name === name) {
                item.hidden = false;

                break;
            }
        }

        for (let item of this.dropdownItemList) {
            if (item.name === name) {
                item.hidden = false;

                break;
            }
        }

        for (let item of this.dropdownEditItemList) {
            if (item.name === name) {
                item.hidden = false;

                break;
            }
        }

        for (let item of this.buttonEditList) {
            if (item.name === name) {
                item.hidden = false;

                break;
            }
        }

        if (this.isRendered()) {
            this.$detailButtonContainer
                .find('li > .action[data-action="'+name+'"]')
                .parent()
                .removeClass('hidden');

            this.$detailButtonContainer
                .find('button.action[data-action="'+name+'"]')
                .removeClass('hidden');

            if (!this.isDropdownItemListEmpty()) {
                this.$dropdownItemListButton.removeClass('hidden');
            }

            if (!this.isDropdownEditItemListEmpty()) {
                this.$dropdownEditItemListButton.removeClass('hidden');
            }

            this.adjustButtons();
        }
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * Disable a button or dropdown action item.
     *
     * @param {string} name A name.
     */
    disableActionItem(name) {
        for (let item of this.buttonList) {
            if (item.name === name) {
                item.disabled = true;

                break;
            }
        }

        for (let item of this.dropdownItemList) {
            if (item.name === name) {
                item.disabled = true;

                break;
            }
        }

        for (let item of this.dropdownEditItemList) {
            if (item.name === name) {
                item.disabled = true;

                break;
            }
        }

        for (let item of this.buttonEditList) {
            if (item.name === name) {
                item.disabled = true;

                break;
            }
        }

        if (this.isRendered()) {
            this.$detailButtonContainer
                .find('li > .action[data-action="'+name+'"]')
                .parent()
                .addClass('disabled')
                .attr('disabled', 'disabled');

            this.$detailButtonContainer
                .find('button.action[data-action="'+name+'"]')
                .addClass('disabled')
                .attr('disabled', 'disabled');
        }
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * Enable a button or dropdown action item.
     *
     * @param {string} name A name.
     */
    enableActionItem(name) {
        for (let item of this.buttonList) {
            if (item.name === name) {
                item.disabled = false;

                break;
            }
        }

        for (let item of this.dropdownItemList) {
            if (item.name === name) {
                item.disabled = false;

                break;
            }
        }

        for (let item of this.dropdownEditItemList) {
            if (item.name === name) {
                item.disabled = false;

                break;
            }
        }

        for (let item of this.buttonEditList) {
            if (item.name === name) {
                item.disabled = false;

                break;
            }
        }

        if (this.isRendered()) {
            this.$detailButtonContainer
                .find('li > .action[data-action="'+name+'"]')
                .parent()
                .removeClass('disabled')
                .removeAttr('disabled');

            this.$detailButtonContainer
                .find('button.action[data-action="'+name+'"]')
                .removeClass('disabled')
                .removeAttr('disabled');
        }
    }

    /**
     * Whether an action item is visible and not disabled.
     *
     * @param {string} name An action item name.
     */
    hasAvailableActionItem(name) {
        if (this.allActionItemsDisabled) {
            return false;
        }

        if (this.type === this.TYPE_DETAIL && this.mode === this.MODE_EDIT) {
            let hasButton = this.buttonEditList
                .findIndex(item => item.name === name && !item.disabled && !item.hidden) !== -1;

            if (hasButton) {
                return true;
            }

            return this.dropdownEditItemList
                .findIndex(item => item.name === name && !item.disabled && !item.hidden) !== -1;
        }

        let hasButton = this.buttonList
            .findIndex(item => item.name === name && !item.disabled && !item.hidden) !== -1;

        if (hasButton) {
            return true;
        }

        return this.dropdownItemList
            .findIndex(item => item.name === name && !item.disabled && !item.hidden) !== -1;
    }

    /**
     * Show a panel.
     *
     * @param {string} name A panel name.
     * @param {module:views/record/detail~panelSoftLockedType} [softLockedType='default']
     *   A soft-locked type.
     */
    showPanel(name, softLockedType) {
        if (this.recordHelper.getPanelStateParam(name, 'hiddenLocked')) {
            return;
        }

        softLockedType = softLockedType || 'default';

        this.recordHelper
            .setPanelStateParam(name,
                'hidden' + Espo.Utils.upperCaseFirst(softLockedType) + 'Locked', false);

        if (softLockedType === 'dynamicLogic') {
            if (this.recordHelper.getPanelStateParam(name, 'hidden') === false) {
                return;
            }
        }

        for (let i = 0; i < this.panelSoftLockedTypeList.length; i++) {
            let iType = this.panelSoftLockedTypeList[i];

            if (iType === softLockedType) {
                continue;
            }

            let iParam = 'hidden' +  Espo.Utils.upperCaseFirst(iType) + 'Locked';

            if (this.recordHelper.getPanelStateParam(name, iParam)) {
                return;
            }
        }

        let middleView = this.getMiddleView();

        if (middleView) {
            middleView.showPanelInternal(name);
        }

        let bottomView = this.getBottomView();

        if (bottomView) {
            if ('showPanel' in bottomView) {
                bottomView.showPanel(name);
            }
        }
        else if (this.bottomView) {
            this.once('ready', () => {
                let view = this.getBottomView();

                if (view) {
                    if ('processShowPanel' in view) {
                        view.processShowPanel(name);

                        return;
                    }

                    if ('showPanel' in view) {
                        view.showPanel(name);
                    }
                }
            });
        }

        let sideView = this.getSideView();

        if (sideView) {
            if ('showPanel' in sideView) {
                sideView.showPanel(name);
            }
        }
        else if (this.sideView) {
            this.once('ready', () => {
                let view = this.getSideView();

                if (view) {
                    if ('processShowPanel' in view) {
                        view.processShowPanel(name);

                        return;
                    }

                    if ('showPanel' in view) {
                        view.showPanel(name);
                    }
                }
            });
        }

        this.recordHelper.setPanelStateParam(name, 'hidden', false);

        if (this.middlePanelDefs[name]) {
            this.controlTabVisibilityShow(this.middlePanelDefs[name].tabNumber);

            this.adjustMiddlePanels();
        }

        this.recordHelper.trigger('panel-show');
    }

    /**
     * Hide a panel.
     *
     * @param {string} name A panel name.
     * @param {boolean} [locked=false] Won't be able to un-hide.
     * @param {module:views/record/detail~panelSoftLockedType} [softLockedType='default']
     *   A soft-locked type.
     */
    hidePanel(name, locked, softLockedType) {
        softLockedType = softLockedType || 'default';

        if (locked) {
            this.recordHelper.setPanelStateParam(name, 'hiddenLocked', true);
        }

        if (softLockedType) {
            this.recordHelper
                .setPanelStateParam(name,
                    'hidden' + Espo.Utils.upperCaseFirst(softLockedType) + 'Locked', true);
        }

        if (softLockedType === 'dynamicLogic') {
            if (this.recordHelper.getPanelStateParam(name, 'hidden') === true) {
                return;
            }
        }

        let middleView = this.getMiddleView();

        if (middleView) {
            middleView.hidePanelInternal(name);
        }

        let bottomView = this.getBottomView();

        if (bottomView) {
            if ('hidePanel' in bottomView) {
                bottomView.hidePanel(name);
            }
        }
        else if (this.bottomView) {
            this.once('ready', () => {
                let view = this.getBottomView();

                if (view) {
                    if ('processHidePanel' in view) {
                        view.processHidePanel(name);

                        return;
                    }

                    if ('hidePanel' in view) {
                        view.hidePanel(name);
                    }
                }
            });
        }

        let sideView = this.getSideView();

        if (sideView) {
            if ('hidePanel' in sideView) {
                sideView.hidePanel(name);
            }
        }
        else if (this.sideView) {
            this.once('ready', () => {
                let view = this.getSideView();

                if (view) {
                    if ('processHidePanel' in view) {
                        view.processHidePanel(name);

                        return;
                    }

                    if ('hidePanel' in view) {
                        view.hidePanel(name);
                    }
                }
            });
        }

        this.recordHelper.setPanelStateParam(name, 'hidden', true);

        if (this.middlePanelDefs[name]) {
            this.controlTabVisibilityHide(this.middlePanelDefs[name].tabNumber);

            this.adjustMiddlePanels();
        }
    }

    afterRender() {
        this.$middle = this.$el.find('.middle');

        if (this.bottomView) {
            this.$bottom = this.$el.find('.bottom');
        }

        this.initElementReferences();

        this.adjustMiddlePanels();
        this.adjustButtons();

        this.initStickableButtonsContainer();
        this.initFieldsControlBehaviour();
    }

    /**
     * @private
     */
    initFieldsControlBehaviour() {
        let fields = this.getFieldViews();

        let fieldInEditMode = null;

        for (let field in fields) {
            let fieldView = fields[field];

            this.listenTo(fieldView, 'edit', (view) => {
                if (fieldInEditMode && fieldInEditMode.isEditMode()) {
                    fieldInEditMode.inlineEditClose();
                }

                fieldInEditMode = view;
            });

            this.listenTo(fieldView, 'inline-edit-on', () => {
                this.inlineEditModeIsOn = true;
            });

            this.listenTo(fieldView, 'inline-edit-off', (o) => {
                o = o || {};

                if (o.all) {
                    return;
                }

                this.inlineEditModeIsOn = false;

                this.setIsNotChanged();
            });

            this.listenTo(fieldView, 'after:inline-edit-off', o => {
                if (this.updatedAttributes && !o.noReset) {
                    this.resetModelChanges();
                }
            });
        }
    }

    /**
     * @private
     */
    initStickableButtonsContainer() {
        let $containers = this.$el.find('.detail-button-container');
        let $container = this.$el.find('.detail-button-container.record-buttons');

        if (!$container.length) {
            return;
        }

        let navbarHeight = this.getThemeManager().getParam('navbarHeight');
        let screenWidthXs = this.getThemeManager().getParam('screenWidthXs');

        let isSmallScreen = $(window.document).width() < screenWidthXs;

        let getOffsetTop = (/** JQuery */$element) => {
            let element = /** @type {HTMLElement} */$element.get(0);

            let value = 0;

            while (element) {
                value += !isNaN(element.offsetTop) ? element.offsetTop : 0;

                element = element.offsetParent;
            }

            if (isSmallScreen) {
                return value;
            }

            return value - navbarHeight;
        };

        let stickTop = getOffsetTop($container);
        let blockHeight = $container.outerHeight();

        stickTop -= 5; // padding;

        let $block = $('<div>')
            .css('height', blockHeight + 'px')
            .html('&nbsp;')
            .hide()
            .insertAfter($container);

        let $middle = this.getMiddleView().$el;
        let $window = $(window);
        let $navbarRight = $('#navbar .navbar-right');

        if (this.stickButtonsFormBottomSelector) {
            let $bottom = this.$el.find(this.stickButtonsFormBottomSelector);

            if ($bottom.length) {
                $middle = $bottom;
            }
        }

        $window.off('scroll.detail-' + this.numId);

        $window.on('scroll.detail-' + this.numId, () => {
            let edge = $middle.position().top + $middle.outerHeight(false) - blockHeight;
            let scrollTop = $window.scrollTop();

            if (scrollTop >= edge && !this.stickButtonsContainerAllTheWay) {
                $containers.hide();
                $navbarRight.removeClass('has-sticked-bar');
                $block.show();

                return;
            }

            if (isSmallScreen && $('#navbar .navbar-body').hasClass('in')) {
                return;
            }

            if (scrollTop > stickTop) {
                if (!$containers.hasClass('stick-sub')) {
                    $containers.addClass('stick-sub');
                    $block.show();

                    /*$('.popover').each((i, el) => {
                        let $el = $(el);
                        $el.css('top', ($el.position().top - blockHeight) + 'px');
                    });*/
                }

                $navbarRight.addClass('has-sticked-bar');

                $containers.show();

                return;
            }

            if ($containers.hasClass('stick-sub')) {
                $containers.removeClass('stick-sub');
                $navbarRight.removeClass('has-sticked-bar');
                $block.hide();

                /*$('.popover').each((i, el) => {
                    let $el = $(el);
                    $el.css('top', ($el.position().top + blockHeight) + 'px');
                });*/
            }

            $containers.show();
        });
    }

    fetch() {
        let data = super.fetch();

        if (this.hasView('side')) {
            let view = this.getSideView();

            if ('fetch' in view) {
                data = _.extend(data, view.fetch());
            }
        }

        if (this.hasView('bottom')) {
            let view = this.getBottomView();

            if ('fetch' in view) {
                data = _.extend(data, view.fetch());
            }
        }

        return data;
    }

    setEditMode() {
        this.trigger('before:set-edit-mode');

        this.inlineEditModeIsOn = false;

        this.$el.find('.record-buttons').addClass('hidden');
        this.$el.find('.edit-buttons').removeClass('hidden');

        return new Promise(resolve => {
            let fields = this.getFieldViews(true);

            let promiseList = [];

            for (let field in fields) {
                let fieldView = fields[field];

                if (fieldView.readOnly) {
                    continue;
                }

                if (fieldView.isEditMode()) {
                    fieldView.fetchToModel();
                    fieldView.removeInlineEditLinks();
                    fieldView.setIsInlineEditMode(false);
                }

                promiseList.push(
                    fieldView
                        .setEditMode()
                        .then(() => {
                            return fieldView.render();
                        })
                );
            }

            this.mode = this.MODE_EDIT;

            this.trigger('after:set-edit-mode');
            this.trigger('after:mode-change');

            Promise.all(promiseList).then(() => resolve());
        });
    }

    setDetailMode() {
        this.trigger('before:set-detail-mode');

        this.$el.find('.edit-buttons').addClass('hidden');
        this.$el.find('.record-buttons').removeClass('hidden');

        this.inlineEditModeIsOn = false;

        return new Promise(resolve => {
            let fields = this.getFieldViews(true);

            let promiseList = [];

            for (let field in fields) {
                let fieldView = fields[field];

                if (!fieldView.isDetailMode()) {
                    if (fieldView.isEditMode()) {
                        fieldView.trigger('inline-edit-off', {
                            all: true,
                        });
                    }

                    promiseList.push(
                        fieldView
                            .setDetailMode()
                            .then(() => fieldView.render())
                    );
                }
            }

            this.mode = this.MODE_DETAIL;

            this.trigger('after:set-detail-mode');
            this.trigger('after:mode-change');

            Promise.all(promiseList).then(() => resolve());
        });
    }

    cancelEdit() {
        this.resetModelChanges();

        this.setDetailMode();
        this.setIsNotChanged();
    }

    resetModelChanges() {
        let skipReRender = true;

        if (this.updatedAttributes) {
            this.attributes = this.updatedAttributes;
            this.updatedAttributes = null;

            skipReRender = false;
        }

        let attributes = this.model.attributes;

        for (let attr in attributes) {
            if (!(attr in this.attributes)) {
                this.model.unset(attr);
            }
        }

        this.model.set(this.attributes, {skipReRender: skipReRender});
    }

    delete() {
        this.confirm({
            message: this.translate('removeRecordConfirmation', 'messages', this.scope),
            confirmText: this.translate('Remove'),
        }, () => {
            this.trigger('before:delete');
            this.trigger('delete');

            Espo.Ui.notify(' ... ');

            let collection = this.model.collection;

            this.model
                .destroy({wait: true})
                .then(() => {
                    if (collection) {
                        if (collection.total > 0) {
                            collection.total--;
                        }
                    }

                    this.model.set('deleted', true, {silent: true});

                    Espo.Ui.success(this.translate('Removed'), {suppress: true});

                    this.trigger('after:delete');
                    this.exit('delete');
                });
        });
    }

    /**
     * Get field views.
     *
     * @param {boolean} [withHidden] With hidden.
     * @return {Object.<string, module:views/fields/base>}
     */
    getFieldViews(withHidden) {
        let fields = {};

        if (this.hasView('middle')) {
            if ('getFieldViews' in this.getMiddleView()) {
                _.extend(fields, Espo.Utils.clone(this.getMiddleView().getFieldViews()));
            }
        }

        if (this.hasView('side')) {
            if ('getFieldViews' in this.getSideView()) {
                _.extend(fields, this.getSideView().getFieldViews(withHidden));
            }
        }

        if (this.hasView('bottom')) {
            if ('getFieldViews' in this.getBottomView()) {
                _.extend(fields, this.getBottomView().getFieldViews(withHidden));
            }
        }

        return fields;
    }

    /**
     * Get a field view.
     *
     * @param {string} name A field name.
     * @return {module:views/fields/base|null}
     */
    getFieldView(name) {
        let view;

        if (this.hasView('middle')) {
            view = (this.getMiddleView().getFieldViews() || {})[name];
        }

        if (!view && this.hasView('side')) {
            view = (this.getSideView().getFieldViews(true) || {})[name];
        }

        if (!view && this.hasView('bottom')) {
            view = (this.getBottomView().getFieldViews(true) || {})[name];
        }

        return view || null;
    }

    // @todo Remove.
    handleDataBeforeRender(data) {}

    data() {
        let navigateButtonsEnabled = !this.navigateButtonsDisabled && !!this.model.collection;

        let previousButtonEnabled = false;
        let nextButtonEnabled = false;

        if (navigateButtonsEnabled) {
            if (this.indexOfRecord > 0) {
                previousButtonEnabled = true;
            }

            if (this.indexOfRecord < this.model.collection.total - 1) {
                nextButtonEnabled = true;
            }
            else {
                if (this.model.collection.total === -1) {
                    nextButtonEnabled = true;
                }
                else if (this.model.collection.total === -2) {
                    if (this.indexOfRecord < this.model.collection.length - 1) {
                        nextButtonEnabled = true;
                    }
                }
            }

            if (!previousButtonEnabled && !nextButtonEnabled) {
                navigateButtonsEnabled = false;
            }
        }

        let hasMiddleTabs = this.hasTabs();
        let middleTabDataList = hasMiddleTabs ? this.getMiddleTabDataList() : [];

        return {
            scope: this.scope,
            entityType: this.entityType,
            buttonList: this.buttonList,
            buttonEditList: this.buttonEditList,
            dropdownItemList: this.dropdownItemList,
            dropdownEditItemList: this.dropdownEditItemList,
            dropdownItemListEmpty: this.isDropdownItemListEmpty(),
            dropdownEditItemListEmpty: this.isDropdownEditItemListEmpty(),
            buttonsDisabled: this.buttonsDisabled,
            id: this.id,
            isWide: this.isWide,
            isSmall: this.type === 'editSmall' || this.type === 'detailSmall',
            navigateButtonsEnabled: navigateButtonsEnabled,
            previousButtonEnabled: previousButtonEnabled,
            nextButtonEnabled: nextButtonEnabled,
            hasMiddleTabs: hasMiddleTabs,
            middleTabDataList: middleTabDataList,
        };
    }

    init() {
        this.entityType = this.model.entityType || this.model.name || 'Common';
        this.scope = this.options.scope || this.entityType;

        this.layoutName = this.options.layoutName || this.layoutName;
        this.detailLayout = this.options.detailLayout || this.detailLayout;

        this.type = this.options.type || this.type;

        this.buttonList = this.options.buttonList || this.buttonList;
        this.dropdownItemList = this.options.dropdownItemList || this.dropdownItemList;

        this.buttonList = Espo.Utils.cloneDeep(this.buttonList);
        this.buttonEditList = Espo.Utils.cloneDeep(this.buttonEditList);
        this.dropdownItemList = Espo.Utils.cloneDeep(this.dropdownItemList);
        this.dropdownEditItemList = Espo.Utils.cloneDeep(this.dropdownEditItemList);

        this.returnAfterCreate = this.options.returnAfterCreate;

        this.returnUrl = this.options.returnUrl || this.returnUrl;
        this.returnDispatchParams = this.options.returnDispatchParams || this.returnDispatchParams;

        this.exit = this.options.exit || this.exit;

        if (this.shortcutKeys) {
            this.shortcutKeys = Espo.Utils.cloneDeep(this.shortcutKeys);
        }
    }

    isDropdownItemListEmpty() {
        if (this.dropdownItemList.length === 0) {
            return true;
        }

        let isEmpty = true;

        this.dropdownItemList.forEach(item => {
            if (!item.hidden) {
                isEmpty = false;
            }
        });

        return isEmpty;
    }

    isDropdownEditItemListEmpty() {
        if (this.dropdownEditItemList.length === 0) {
            return true;
        }

        let isEmpty = true;

        this.dropdownEditItemList.forEach(item => {
            if (!item.hidden) {
                isEmpty = false;
            }
        });

        return isEmpty;
    }

    setup() {
        if (typeof this.model === 'undefined') {
            throw new Error('Model has not been injected into record view.');
        }

        this.recordHelper = this.options.recordHelper ||
            new ViewRecordHelper(this.defaultFieldStates, this.defaultFieldStates);

        this._initInlineEditSave();

        let collection = this.collection = this.model.collection;

        if (collection) {
            this.listenTo(this.model, 'destroy', () => {
                collection.remove(this.model.id);
                collection.trigger('sync', {});
            });

            if ('indexOfRecord' in this.options) {
                this.indexOfRecord = this.options.indexOfRecord;
            } else {
                this.indexOfRecord = collection.indexOf(this.model);
            }
        }

        /** @type {Object.<string,*>|null} */
        this.middlePanelDefs = {};

        /** @type {Object.<string,*>[]} */
        this.middlePanelDefsList = [];

        if (this.getUser().isPortal() && !this.portalLayoutDisabled) {
            if (
                this.getMetadata().get(
                    ['clientDefs', this.scope, 'additionalLayouts', this.layoutName + 'Portal']
                )
            ) {
                this.layoutName += 'Portal';
            }
        }

        this.numId = Math.floor((Math.random() * 10000) + 1);

        // For testing purpose.
        $(window).on('fetch-record.' + this.cid, () => this.handleRecordUpdate());

        this.once('remove', () => {
            if (this.isChanged) {
                this.resetModelChanges();
            }
            this.setIsNotChanged();

            $(window).off('scroll.detail-' + this.numId);
            $(window).off('fetch-record.' + this.cid);
        });

        this.id = Espo.Utils.toDom(this.entityType) + '-' +
            Espo.Utils.toDom(this.type) + '-' + this.numId;

        this.isNew = this.model.isNew();

        if (!this.editModeDisabled) {
            if ('editModeDisabled' in this.options) {
                this.editModeDisabled = this.options.editModeDisabled;
            }
        }

        this.confirmLeaveDisabled = this.options.confirmLeaveDisabled || this.confirmLeaveDisabled;

        this.buttonsDisabled = this.options.buttonsDisabled || this.buttonsDisabled;

        // for backward compatibility
        // @todo remove
        if ('buttonsPosition' in this.options && !this.options.buttonsPosition) {
            this.buttonsDisabled = true;
        }

        if ('isWide' in this.options) {
            this.isWide = this.options.isWide;
        }

        if ('sideView' in this.options) {
            this.sideView = this.options.sideView;
        }

        if ('bottomView' in this.options) {
            this.bottomView = this.options.bottomView;
        }

        this.sideDisabled = this.options.sideDisabled || this.sideDisabled;
        this.bottomDisabled = this.options.bottomDisabled || this.bottomDisabled;

        this.readOnly = this.options.readOnly || this.readOnly;

        if (!this.readOnly && !this.isNew) {
            this.readOnly = this.getMetadata()
                .get(['clientDefs', this.scope, 'editDisabled']) || false;
        }

        if (this.getMetadata().get(['clientDefs', this.scope, 'createDisabled'])) {
            this.duplicateAction = false;
        }

        if ((this.getConfig().get('currencyList') || []).length <= 1) {
            this.convertCurrencyAction = false;
        }

        this.readOnlyLocked = this.readOnly;

        this.inlineEditDisabled = this.inlineEditDisabled ||
            this.getMetadata().get(['clientDefs', this.scope, 'inlineEditDisabled']) ||
            false;

        this.inlineEditDisabled = this.options.inlineEditDisabled || this.inlineEditDisabled;
        this.navigateButtonsDisabled = this.options.navigateButtonsDisabled ||
            this.navigateButtonsDisabled;
        this.portalLayoutDisabled = this.options.portalLayoutDisabled || this.portalLayoutDisabled;
        this.dynamicLogicDefs = this.options.dynamicLogicDefs || this.dynamicLogicDefs;

        this.accessControlDisabled = this.options.accessControlDisabled || this.accessControlDisabled;

        this.setupActionItems();
        this.setupBeforeFinal();

        this.on('after:render', () => {
            this.initElementReferences();
        });

        if (
            !this.isNew &&
            !!this.getHelper().webSocketManager &&
            this.getMetadata().get(['scopes', this.entityType, 'object'])
        ) {
            this.subscribeToWebSocket();

            this.once('remove', () => {
                if (this.isSubscribedToWebSocket) {
                    this.unsubscribeFromWebSocket();
                }
            });
        }

        this.wait(
            this.getHelper().processSetupHandlers(this, this.setupHandlerType)
        );

        this.initInlineEditDynamicWithLogicInteroperability();

        this.forcePatchAttributeDependencyMap = this.getMetadata()
            .get(['clientDefs', this.scope, 'forcePatchAttributeDependencyMap']) || {};
    }

    setupBeforeFinal() {
        if (!this.accessControlDisabled) {
            this.manageAccess();
        }

        this.attributes = this.model.getClonedAttributes();

        if (this.options.attributes) {
            this.model.set(this.options.attributes);
        }

        this.listenTo(this.model, 'sync', () => {
            this.attributes = this.model.getClonedAttributes();
        });

        this.listenTo(this.model, 'change', (m, o) => {
            if (o.sync) {
                for (let attribute in m.attributes) {
                    if (!m.hasChanged(attribute)) {
                        continue;
                    }

                    this.attributes[attribute] = Espo.Utils.cloneDeep(
                        m.get(attribute)
                    );
                }

                return;
            }

            if (this.mode === this.MODE_EDIT || this.inlineEditModeIsOn) {
                this.setIsChanged();
            }
        });

        let dependencyDefs = Espo.Utils.clone(
            this.getMetadata().get(['clientDefs', this.entityType, 'formDependency']) || {}
        );

        // noinspection JSDeprecatedSymbols
        this.dependencyDefs = _.extend(dependencyDefs, this.dependencyDefs);

        this.initDependency();

        let dynamicLogic = Espo.Utils.clone(
            this.getMetadata().get(['clientDefs', this.entityType, 'dynamicLogic']) || {}
        );

        this.dynamicLogicDefs = _.extend(dynamicLogic, this.dynamicLogicDefs);

        this.initDynamicLogic();
        this.setupFieldLevelSecurity();
        this.initDynamicHandler();
    }

    /**
     * @private
     */
    _initInlineEditSave() {
        this.listenTo(this.recordHelper, 'inline-edit-save', (field, o) => {
            this.inlineEditSave(field, o);
        });
    }

    /**
     * @param {string} field
     * @param {module:views/record/base~saveOptions} [options]
     */
    inlineEditSave(field, options) {
        let view = this.getFieldView(field);

        if (!view) {
            throw new Error(`No field '${field}'.`);
        }

        options = _.extend({
            inline: true,
            field: field,
            afterValidate: () => {
                if (options.bypassClose) {
                    return;
                }

                view.inlineEditClose(true)
            },
        }, options || {});

        this.save(options)
            .then(() => {
                view.trigger('after:inline-save');
                view.trigger('after:save');


                if (options.bypassClose) {
                    view.initialAttributes = this.model.getClonedAttributes();
                }
            })
            .catch(reason => {
                if (reason === 'notModified') {
                    if (options.bypassClose) {
                        return;
                    }

                    view.inlineEditClose(true);

                    return;
                }

                if (reason === 'error') {
                    if (options.bypassClose) {
                        return;
                    }

                    view.inlineEdit();
                }
            });
    }

    /**
     * @private
     */
    initInlineEditDynamicWithLogicInteroperability() {
        let blockEdit = false;

        let process = (type, field) => {
            if (!this.inlineEditModeIsOn || this.editModeDisabled) {
                return;
            }

            if (blockEdit) {
                return;
            }

            if (type === 'required') {
                let fieldView = this.getFieldView(field);

                if (fieldView.validateRequired) {
                    fieldView.suspendValidationMessage();

                    try {
                        if (!fieldView.validateRequired()) {
                            return;
                        }
                    }
                    catch (e) {}
                }
            }

            blockEdit = true;

            setTimeout(() => blockEdit = false, 300);

            setTimeout(() => {
                this.setEditMode();

                this.getFieldViewList()
                    .forEach(view => view.removeInlineEditLinks());
            }, 10);
        };

        this.on('set-field-required', field => process('required', field));
        this.on('set-field-option-list', field => process('options', field));
        this.on('reset-field-option-list', field => process('options', field));
    }

    /**
     * @private
     */
    initDynamicHandler() {
        let dynamicHandlerClassName = this.dynamicHandlerClassName ||
            this.getMetadata().get(['clientDefs', this.scope, 'dynamicHandler']);

        let init = dynamicHandler => {
            this.listenTo(this.model, 'change', (model, o) => {
                if ('onChange' in dynamicHandler) {
                    dynamicHandler.onChange.call(dynamicHandler, model, o);
                }

                let changedAttributes = model.changedAttributes();

                for (let attribute in changedAttributes) {
                    let methodName = 'onChange' + Espo.Utils.upperCaseFirst(attribute);

                    if (methodName in dynamicHandler) {
                        dynamicHandler[methodName]
                            .call(dynamicHandler, model, changedAttributes[attribute], o);
                    }
                }
            });

            if ('init' in dynamicHandler) {
                dynamicHandler.init();
            }
        };

        if (dynamicHandlerClassName) {
            this.wait(
                new Promise(resolve => {
                    Espo.loader.require(dynamicHandlerClassName, DynamicHandler => {
                        let dynamicHandler = this.dynamicHandler = new DynamicHandler(this);

                        init(dynamicHandler);

                        resolve();
                    });
                })
            );
        }

        let handlerList = this.getMetadata().get(['clientDefs', this.scope, 'dynamicHandlerList']) || [];

        if (handlerList.length) {
            let self = this;

            let promiseList = [];

            handlerList.forEach((className) => {
                promiseList.push(
                    new Promise(resolve => {
                        Espo.loader.require(className, DynamicHandler => {
                            resolve(new DynamicHandler(self));
                        });
                    })
                );
            });

            this.wait(
                Promise.all(promiseList).then(list => {
                    list.forEach((dynamicHandler) => {
                        init(dynamicHandler);
                    });
                })
            );
        }
    }

    setupFinal() {
        this.build();

        if (this.shortcutKeys && this.options.shortcutKeysEnabled) {
            this.events['keydown.record-detail'] = e => {
                let key = Espo.Utils.getKeyFromKeyEvent(e);

                if (typeof this.shortcutKeys[key] === 'function') {
                    this.shortcutKeys[key].call(this, e.originalEvent);

                    return;
                }

                let actionName = this.shortcutKeys[key];

                if (!actionName) {
                    return;
                }

                e.preventDefault();
                e.stopPropagation();

                let methodName = 'action' + Espo.Utils.upperCaseFirst(actionName);

                if (typeof this[methodName] === 'function') {
                    this[methodName]();

                    return;
                }

                this[actionName]();
            };
        }

        if (!this.options.focusForCreate) {
            this.once('after:render', () => this.focusOnFirstDiv());
        }
    }

    setIsChanged() {
        this.isChanged = true;

        if (this.confirmLeaveDisabled) {
            return;
        }

        this.setConfirmLeaveOut(true);
    }

    setIsNotChanged() {
        this.isChanged = false;

        if (this.confirmLeaveDisabled) {
            return;
        }

        this.setConfirmLeaveOut(false);
    }

    switchToModelByIndex(indexOfRecord) {
        let collection = this.model.collection || this.collection;

        if (!collection) {
            return;
        }

        let model = collection.at(indexOfRecord);

        if (!model) {
            throw new Error("Model is not found in collection by index.");
        }

        let id = model.id;
        let scope = this.entityType || this.scope;

        this.getRouter().navigate('#' + scope + '/view/' + id, {trigger: false});

        this.getRouter().dispatch(scope, 'view', {
            id: id,
            model: model,
            indexOfRecord: indexOfRecord,
            rootUrl: this.options.rootUrl,
        });
    }

    actionPrevious() {
        this.model.abortLastFetch();

        let collection;

        if (!this.model.collection) {
            collection = this.collection;

            if (!collection) {
                return;
            }

            this.indexOfRecord--;

            if (this.indexOfRecord < 0) {
                this.indexOfRecord = 0;
            }
        }

        if (!(this.indexOfRecord > 0)) {
            return;
        }

        let indexOfRecord = this.indexOfRecord - 1;

        this.switchToModelByIndex(indexOfRecord);
    }

    actionNext() {
        this.model.abortLastFetch();

        let collection;

        if (!this.model.collection) {
            collection = this.collection;

            if (!collection) {
                return;
            }

            this.indexOfRecord--;

            if (this.indexOfRecord < 0) {
                this.indexOfRecord = 0;
            }
        }
        else {
            collection = this.model.collection;
        }

        if (!(this.indexOfRecord < collection.total - 1) && collection.total >= 0) {
            return;
        }

        if (collection.total === -2 && this.indexOfRecord >= collection.length - 1) {
            return;
        }

        let indexOfRecord = this.indexOfRecord + 1;

        if (indexOfRecord <= collection.length - 1) {
            this.switchToModelByIndex(indexOfRecord);

            return;
        }

        collection
            .fetch({
                more: true,
                remove: false,
            })
            .then(() => {
                this.switchToModelByIndex(indexOfRecord);
            });
    }

    // noinspection JSUnusedGlobalSymbols
    actionViewPersonalData() {
        this.createView('viewPersonalData', 'views/personal-data/modals/personal-data', {
            model: this.model
        }, view => {
            view.render();

            this.listenToOnce(view, 'erase', () => {
                this.clearView('viewPersonalData');
                this.model.fetch();
            });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionViewFollowers(data) {
        let viewName = this.getMetadata().get(
                ['clientDefs', this.entityType, 'relationshipPanels', 'followers', 'viewModalView']
            ) ||
            this.getMetadata().get(['clientDefs', 'User', 'modalViews', 'relatedList']) ||
            'views/modals/followers-list';

        let selectDisabled =
            !this.getUser().isAdmin() &&
            this.getAcl().getPermissionLevel('followerManagementPermission') === 'no' &&
            this.getAcl().getPermissionLevel('portalPermission') === 'no';

        let options = {
            model: this.model,
            link: 'followers',
            scope: 'User',
            title: this.translate('Followers'),
            filtersDisabled: true,
            url: this.entityType + '/' + this.model.id + '/followers',
            createDisabled: true,
            selectDisabled: selectDisabled,
            rowActionsView: 'views/user/record/row-actions/relationship-followers',
        };

        if (data.viewOptions) {
            for (let item in data.viewOptions) {
                options[item] = data.viewOptions[item];
            }
        }

        Espo.Ui.notify(' ... ');

        this.createView('modalRelatedList', viewName, options, view => {
            Espo.Ui.notify(false);

            view.render();

            this.listenTo(view, 'action', (event, element) => {
                Espo.Utils.handleAction(this, event, element);
            });

            this.listenToOnce(view, 'close', () => {
                this.clearView('modalRelatedList');
            });

            view.listenTo(this.model, 'after:relate:followers', () => {
                this.model.fetch();
            });

            view.listenTo(this.model, 'after:unrelate:followers', () => {
                this.model.fetch();
            });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionPrintPdf() {
        this.createView('pdfTemplate', 'views/modals/select-template', {
            entityType: this.entityType,
        }, (view) => {
            view.render();

            this.listenToOnce(view, 'select', (model) => {
                this.clearView('pdfTemplate');

                window.open(
                    '?entryPoint=pdf&entityType=' +
                    this.entityType + '&entityId=' +
                    this.model.id + '&templateId=' + model.id, '_blank'
                );
            });
        });
    }

    afterSave() {
        if (this.isNew) {
            Espo.Ui.success(this.translate('Created'));
        }
        else {
            Espo.Ui.success(this.translate('Saved'));
        }

        this.enableActionItems();

        this.setIsNotChanged();

        setTimeout(() => {
            this.unblockUpdateWebSocket();
        }, this.blockUpdateWebSocketPeriod);
    }

    beforeSave() {
        Espo.Ui.notify(this.translate('saving', 'messages'));

        this.blockUpdateWebSocket();
    }

    beforeBeforeSave() {
        this.disableActionItems();
    }

    afterSaveError() {
        this.enableActionItems();
    }

    afterNotModified() {
        let msg = this.translate('notModified', 'messages');

        Espo.Ui.warning(msg);

        this.enableActionItems();
        this.setIsNotChanged();
    }

    afterNotValid() {
        Espo.Ui.error(this.translate('Not valid'))

        this.enableActionItems();
    }

    errorHandlerDuplicate(duplicates, o, resolve) {
        Espo.Ui.notify(false);

        this.createView('duplicate', 'views/modals/duplicate', {
            scope: this.entityType,
            duplicates: duplicates,
            model: this.model,
        }, view => {
            view.render();

            this.listenToOnce(view, 'save', () => {
                this.actionSave({
                    options: {
                        headers: {
                            'X-Skip-Duplicate-Check': 'true',
                        }
                    }
                }).then(() => resolve());
            });
        });

        return true;
    }

    // noinspection JSUnusedGlobalSymbols
    errorHandlerModified(data, options) {
        Espo.Ui.notify(false);

        let versionNumber = data.versionNumber;
        let values = data.values || {};

        let attributeList = Object.keys(values);

        let diffAttributeList = [];

        attributeList.forEach(attribute => {
            if (this.attributes[attribute] !== values[attribute]) {
                diffAttributeList.push(attribute);
            }
        });

        if (diffAttributeList.length === 0) {
            setTimeout(() => {
                this.model.set('versionNumber', versionNumber, {silent: true});
                this.attributes.versionNumber = versionNumber;

                if (options.inline && options.field) {
                    this.inlineEditSave(options.field);

                    return;
                }

                this.actionSave();
            }, 5);

            return;
        }

        this.createView(
            'dialog',
            'views/modals/resolve-save-conflict',
            {
                model: this.model,
                attributeList: diffAttributeList,
                currentAttributes: Espo.Utils.cloneDeep(this.model.attributes),
                originalAttributes: Espo.Utils.cloneDeep(this.attributes),
                actualAttributes: Espo.Utils.cloneDeep(values),
            }
        )
        .then(view => {
            view.render();

            this.listenTo(view, 'resolve', () => {
                this.model.set('versionNumber', versionNumber, {silent: true});
                this.attributes.versionNumber = versionNumber;

                for (let attribute in values) {
                    this.setInitialAttributeValue(attribute, values[attribute]);
                }
            });
        });
    }

    /**
     * Get a middle view.
     *
     * @return {module:views/record/detail-middle}
     */
    getMiddleView() {
        return this.getView('middle');
    }

    /**
     * Get a side view.
     *
     * @protected
     * @return {module:views/record/detail-side}
     */
    getSideView() {
        return this.getView('side');
    }

    /**
     * Get a bottom view.
     *
     * @protected
     * @return {module:views/record/detail-bottom}
     */
    getBottomView() {
        return this.getView('bottom');
    }

    setReadOnly() {
        if (!this.readOnlyLocked) {
            this.readOnly = true;
        }

        let bottomView = this.getBottomView();

        if (bottomView && 'setReadOnly' in bottomView) {
            bottomView.setReadOnly();
        }

        let sideView = this.getSideView();

        if (sideView && 'setReadOnly' in sideView) {
            sideView.setReadOnly();
        }

        this.getFieldList().forEach((field) => {
            this.setFieldReadOnly(field);
        });
    }

    setNotReadOnly(onlyNotSetAsReadOnly) {
        if (!this.readOnlyLocked) {
            this.readOnly = false;
        }

        let bottomView = this.getBottomView();

        if (bottomView && 'setNotReadOnly' in bottomView) {
            bottomView.setNotReadOnly(onlyNotSetAsReadOnly);
        }

        let sideView = this.getSideView();

        if (sideView && 'setNotReadOnly' in sideView) {
            sideView.setNotReadOnly(onlyNotSetAsReadOnly);
        }

        this.getFieldList().forEach((field) => {
            if (onlyNotSetAsReadOnly) {
                if (this.recordHelper.getFieldStateParam(field, 'readOnly')) {
                    return;
                }
            }

            this.setFieldNotReadOnly(field);
        });
    }

    manageAccessEdit(second) {
        if (this.isNew) {
            return;
        }

        let editAccess = this.getAcl().checkModel(this.model, 'edit', true);

        if (!editAccess || this.readOnlyLocked) {
            this.readOnly = true;

            this.hideActionItem('edit');

            if (this.selfAssignAction) {
                this.hideActionItem('selfAssign');
            }
        } else {
            this.showActionItem('edit');

            if (this.selfAssignAction) {
                this.hideActionItem('selfAssign');

                if (this.model.has('assignedUserId')) {
                    if (!this.model.get('assignedUserId')) {
                        this.showActionItem('selfAssign');
                    }
                }
            }

            if (!this.readOnlyLocked) {
                if (this.readOnly && second) {
                    if (this.isReady) {
                        this.setNotReadOnly(true);
                    }
                    else {
                        this.on('ready', () => this.setNotReadOnly(true));
                    }
                }

                this.readOnly = false;
            }
        }

        if (editAccess === null) {
            this.listenToOnce(this.model, 'sync', () => {
                this.manageAccessEdit(true);
            });
        }
    }

    manageAccessDelete() {
        if (this.isNew) {
            return;
        }

        let deleteAccess = this.getAcl().checkModel(this.model, 'delete', true);

        if (!deleteAccess) {
            this.hideActionItem('delete');
        } else {
            this.showActionItem('delete');
        }

        if (deleteAccess === null) {
            this.listenToOnce(this.model, 'sync', () => {
                this.manageAccessDelete(true);
            });
        }
    }

    manageAccessStream() {
        if (this.isNew) {
            return;
        }

        if (
            ~['no', 'own'].indexOf(this.getAcl().getLevel('User', 'read'))
            &&
            this.getAcl().getPermissionLevel('portalPermission') === 'no'
        ) {
            this.hideActionItem('viewFollowers');

            return;
        }

        let streamAccess = this.getAcl().checkModel(this.model, 'stream', true);

        if (!streamAccess) {
            this.hideActionItem('viewFollowers');
        } else {
            this.showActionItem('viewFollowers');
        }

        if (streamAccess === null) {
            this.listenToOnce(this.model, 'sync', () => {
                this.manageAccessStream(true);
            });
        }
    }

    manageAccess() {
        this.manageAccessEdit();
        this.manageAccessDelete();
        this.manageAccessStream();
    }

    /**
     * Add a button.
     *
     * @param {module:views/record/detail~button} o
     * @param {boolean} [toBeginning]
     */
    addButton(o, toBeginning) {
        let name = o.name;

        if (!name) {
            return;
        }

        for (let item of this.buttonList) {
            if (item.name === name) {
                return;
            }
        }

        toBeginning ?
            this.buttonList.unshift(o) :
            this.buttonList.push(o);
    }

    /**
     * Add a dropdown item.
     *
     * @param {module:views/record/detail~dropdownItem|false} o
     * @param {boolean} [toBeginning]
     */
    addDropdownItem(o, toBeginning) {
        if (!o) {
            toBeginning ?
                this.dropdownItemList.unshift(false) :
                this.dropdownItemList.push(false);

            return;
        }

        let name = o.name;

        if (!name) {
            return;
        }

        for (let item of this.dropdownItemList) {
            if (item.name === name) {
                return;
            }
        }

        toBeginning ?
            this.dropdownItemList.unshift(o) :
            this.dropdownItemList.push(o);
    }

    /**
     * Add an 'edit' mode button.
     *
     * @param {module:views/record/detail~button} o
     * @param {boolean} [toBeginning]
     */
    addButtonEdit(o, toBeginning) {
        let name = o.name;

        if (!name) {
            return;
        }

        for (let item of this.buttonEditList) {
            if (item.name === name) {
                return;
            }
        }

        toBeginning ?
            this.buttonEditList.unshift(o) :
            this.buttonEditList.push(o);
    }

    /**
     * @deprecated Use `enableActionItems`.
     */
    enableButtons() {
        this.allActionItemsDisabled = false;

        this.$el.find(".button-container .actions-btn-group .action")
            .removeAttr('disabled')
            .removeClass('disabled');

        this.$el.find(".button-container .actions-btn-group .dropdown-toggle")
            .removeAttr('disabled')
            .removeClass('disabled');

        this.buttonList
            .filter(item => item.disabled)
            .forEach(item => {
                this.$detailButtonContainer
                    .find(`button.action[data-action="${item.name}"]`)
                    .addClass('disabled')
                    .attr('disabled', 'disabled');
            });

        this.buttonEditList
            .filter(item => item.disabled)
            .forEach(item => {
                this.$detailButtonContainer
                    .find(`button.action[data-action="${item.name}"]`)
                    .addClass('disabled')
                    .attr('disabled', 'disabled');
            });

        this.dropdownItemList
            .filter(item => item.disabled)
            .forEach(item => {
                this.$detailButtonContainer
                    .find(`li > .action[data-action="${item.name}"]`)
                    .parent()
                    .addClass('disabled')
                    .attr('disabled', 'disabled');
            });

        this.dropdownEditItemList
            .filter(item => item.disabled)
            .forEach(item => {
                this.$detailButtonContainer
                    .find(`li > .action[data-action="${item.name}"]`)
                    .parent()
                    .addClass('disabled')
                    .attr('disabled', 'disabled');
            });
    }

    /**
     * @deprecated Use `disableActionItems`.
     */
    disableButtons() {
        this.allActionItemsDisabled = true;

        this.$el.find(".button-container .actions-btn-group .action")
            .attr('disabled', 'disabled')
            .addClass('disabled');

        this.$el.find(".button-container .actions-btn-group .dropdown-toggle")
            .attr('disabled', 'disabled')
            .addClass('disabled');
    }

    /**
     * Remove a button or dropdown item.
     *
     * @param {string} name A name.
     */
    removeActionItem(name) {
        // noinspection JSDeprecatedSymbols
        this.removeButton(name);
    }

    /**
     * @deprecated Use `removeActionItem`.
     *
     * @param {string} name A name.
     */
    removeButton(name) {
        for (const [i, item] of this.buttonList.entries()) {
            if (item.name === name) {
                this.buttonList.splice(i, 1);

                break;
            }
        }

        for (const [i, item] of this.dropdownItemList.entries()) {
            if (item.name === name) {
                this.dropdownItemList.splice(i, 1);

                break;
            }
        }

        if (this.isRendered()) {
            this.$el.find('.detail-button-container .action[data-action="'+name+'"]').remove();
        }
    }

    /**
     * Convert a detail layout to an internal layout.
     *
     * @protected
     * @param {module:views/record/detail~panelDefs[]} simplifiedLayout A detail layout.
     * @return {Object[]}
     */
    convertDetailLayout(simplifiedLayout) {
        let layout = [];
        let el = this.getSelector() || '#' + (this.id);

        this.panelFieldListMap = {};

        let tabNumber = -1;

        for (let p = 0; p < simplifiedLayout.length; p++) {
            let item = simplifiedLayout[p];

            let panel = {};

            let tabBreak = item.tabBreak || p === 0;

            if (tabBreak) {
                tabNumber++;
            }

            if ('customLabel' in item) {
                panel.label = item.customLabel;

                if (panel.label) {
                    panel.label = this.getLanguage()
                        .translate(panel.label, 'panelCustomLabels', this.entityType);
                }
            } else {
                panel.label = item.label || null;

                if (panel.label) {
                    panel.label = this.getLanguage()
                        .translate(panel.label, 'labels', this.entityType);
                }
            }

            panel.name = item.name || 'panel-' + p.toString();
            panel.style = item.style || 'default';
            panel.rows = [];
            panel.tabNumber = tabNumber;

            this.middlePanelDefs[panel.name] = {
                name: panel.name,
                style: panel.style,
                tabNumber: panel.tabNumber,
                tabBreak: tabBreak,
                tabLabel: item.tabLabel,
            };

            this.middlePanelDefsList.push(this.middlePanelDefs[panel.name]);

            // noinspection JSUnresolvedReference
            if (item.dynamicLogicVisible && this.dynamicLogic) {
                this.dynamicLogic.addPanelVisibleCondition(panel.name, item.dynamicLogicVisible);
            }

            // noinspection JSUnresolvedReference
            if (item.dynamicLogicStyled && this.dynamicLogic) {
                this.dynamicLogic.addPanelStyledCondition(panel.name, item.dynamicLogicStyled);
            }

            // noinspection JSUnresolvedReference
            if (item.hidden && tabNumber === 0) {
                panel.hidden = true;

                this.hidePanel(panel.name);

                this.underShowMoreDetailPanelList = this.underShowMoreDetailPanelList || [];
                this.underShowMoreDetailPanelList.push(panel.name);
            }

            let lType = 'rows';

            if (item.columns) {
                lType = 'columns';

                panel.columns = [];
            }

            if (panel.name) {
                this.panelFieldListMap[panel.name] = [];
            }

            for (const [i, itemI] of item[lType].entries()) {
                let row = [];

                for (const cellDefs of itemI) {
                    if (cellDefs === false) {
                        row.push(false);

                        continue;
                    }

                    let view = cellDefs.view;
                    let name = cellDefs.name;

                    if (!name && view && typeof view === 'object') {
                        name = view.name;
                    }

                    if (!name) {
                        console.warn(`No 'name' specified in detail layout cell.`);

                        continue;
                    }

                    let selector;

                    if (view && typeof view === 'object') {
                        view.model = this.model;
                        view.mode = this.fieldsMode;

                        selector = `.field[data-name="${name}"]`;
                    }

                    if (panel.name) {
                        this.panelFieldListMap[panel.name].push(name);
                    }

                    let type = cellDefs.type || this.model.getFieldType(name) || 'base';

                    view = view ||
                        this.model.getFieldParam(name, 'view') ||
                        this.getFieldManager().getViewName(type);

                    let o = {
                        fullSelector: el + ' .middle .field[data-name="' + name + '"]',
                        defs: {
                            name: name,
                            params: cellDefs.params || {},
                        },
                        mode: this.fieldsMode,
                    };

                    if (this.readOnly) {
                        o.readOnly = true;
                    }

                    if (cellDefs.readOnly) {
                        o.readOnly = true;
                        o.readOnlyLocked = true;
                    }

                    if (this.readOnlyLocked) {
                        o.readOnlyLocked = true;
                    }

                    if (this.inlineEditDisabled || cellDefs.inlineEditDisabled) {
                        o.inlineEditDisabled = true;
                    }

                    // noinspection JSUnresolvedReference
                    let fullWidth = cellDefs.fullWidth || false;

                    if (!fullWidth) {
                        if (item[lType][i].length === 1) {
                            fullWidth = true;
                        }
                    }

                    if (this.recordHelper.getFieldStateParam(name, 'hidden')) {
                        o.disabled = true;
                    }

                    if (this.recordHelper.getFieldStateParam(name, 'hiddenLocked')) {
                        o.disabledLocked = true;
                    }

                    if (this.recordHelper.getFieldStateParam(name, 'readOnly')) {
                        o.readOnly = true;
                    }

                    if (!o.readOnlyLocked && this.recordHelper.getFieldStateParam(name, 'readOnlyLocked')) {
                        o.readOnlyLocked = true;
                    }

                    if (this.recordHelper.getFieldStateParam(name, 'required') !== null) {
                        o.defs.params = o.defs.params || {};
                        o.defs.params.required = this.recordHelper.getFieldStateParam(name, 'required');
                    }

                    if (this.recordHelper.hasFieldOptionList(name)) {
                        o.customOptionList = this.recordHelper.getFieldOptionList(name);
                    }

                    o.validateCallback = () => this.validateField(name);

                    o.recordHelper = this.recordHelper;
                    o.dataObject = this.options.dataObject || {};

                    if (cellDefs.options) {
                        for (let optionName in cellDefs.options) {
                            if (typeof o[optionName] !== 'undefined') {
                                continue;
                            }

                            o[optionName] = cellDefs.options[optionName];
                        }
                    }

                    let cell = {
                        name: name + 'Field',
                        view: view,
                        field: name,
                        fullSelector: el + ' .middle .field[data-name="' + name + '"]',
                        fullWidth: fullWidth,
                        options: o,
                    };

                    if (selector) {
                        cell.selector = selector;
                    }

                    if ('labelText' in cellDefs) {
                        o.labelText = cellDefs.labelText;
                        cell.customLabel = cellDefs.labelText;
                    }

                    if ('customLabel' in cellDefs) {
                        cell.customLabel = cellDefs.customLabel;
                    }

                    if ('label' in cellDefs) {
                        cell.label = cellDefs.label;
                    }

                    if (
                        view &&
                        typeof view === 'object' &&
                        !cell.customLabel &&
                        !cell.label &&
                        view.getLabelText()
                    ) {
                        cell.customLabel = view.getLabelText();
                    }

                    if ('customCode' in cellDefs) {
                        cell.customCode = cellDefs.customCode;
                    }

                    if ('noLabel' in cellDefs) {
                        cell.noLabel = cellDefs.noLabel;
                    }

                    if ('span' in cellDefs) {
                        cell.span = cellDefs.span;
                    }

                    row.push(cell);
                }

                panel[lType].push(row);
            }

            layout.push(panel);
        }

        return layout;
    }

    /**
     * @private
     * @param {function(Object[]): void}callback
     */
    getGridLayout(callback) {
        if (this.gridLayout !== null) {
            callback(this.gridLayout);

            return;
        }

        if (this.detailLayout) {
            this.gridLayout = {
                type: this.gridLayoutType,
                layout: this.convertDetailLayout(this.detailLayout),
            };

            callback(this.gridLayout);

            return;
        }

        this.getHelper().layoutManager.get(this.entityType, this.layoutName, detailLayout => {
            if (typeof this.modifyDetailLayout === 'function') {
                detailLayout = Espo.Utils.cloneDeep(detailLayout);

                this.modifyDetailLayout(detailLayout);
            }

            this.detailLayout = detailLayout;

            this.gridLayout = {
                type: this.gridLayoutType,
                layout: this.convertDetailLayout(this.detailLayout),
            };

            callback(this.gridLayout);
        });
    }

    /**
     * Create a side view.
     *
     * @protected
     */
    createSideView() {
        let el = this.getSelector() || '#' + (this.id);

        this.createView('side', this.sideView, {
            model: this.model,
            scope: this.scope,
            fullSelector: el + ' .side',
            type: this.type,
            readOnly: this.readOnly,
            inlineEditDisabled: this.inlineEditDisabled,
            recordHelper: this.recordHelper,
            recordViewObject: this,
            isReturn: this.options.isReturn,
            dataObject: this.options.dataObject,
        });
    }

    /**
     * Create a middle view.
     *
     * @protected
     */
    createMiddleView(callback) {
        let el = this.getSelector() || '#' + (this.id);

        this.waitForView('middle');

        this.getGridLayout(layout => {
            if (
                this.hasTabs() &&
                this.options.isReturn &&
                this.isStoredTabForThisRecord()
            ) {
                this.selectStoredTab();
            }

            this.createView('middle', this.middleView, {
                model: this.model,
                scope: this.scope,
                type: this.type,
                layoutDefs: layout,
                fullSelector: el + ' .middle',
                layoutData: {
                    model: this.model,
                },
                recordHelper: this.recordHelper,
                recordViewObject: this,
                panelFieldListMap: this.panelFieldListMap,
            }, callback);
        });
    }

    /**
     * Create a bottom view.
     *
     * @protected
     */
    createBottomView() {
        let el = this.getSelector() || '#' + (this.id);

        this.createView('bottom', this.bottomView, {
            model: this.model,
            scope: this.scope,
            fullSelector: el + ' .bottom',
            readOnly: this.readOnly,
            type: this.type,
            inlineEditDisabled: this.inlineEditDisabled,
            recordHelper: this.recordHelper,
            recordViewObject: this,
            portalLayoutDisabled: this.portalLayoutDisabled,
            isReturn: this.options.isReturn,
            dataObject: this.options.dataObject,
        });
    }

    /**
     * Create views.
     *
     * @protected
     * @param {function(module:views/record/detail-middle): void} [callback]
     */
    build(callback) {
        if (!this.sideDisabled && this.sideView) {
            this.createSideView();
        }

        if (this.middleView) {
            this.createMiddleView(callback);
        }

        if (!this.bottomDisabled && this.bottomView) {
            this.createBottomView();
        }
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * Called after create.
     *
     * @return {boolean} True if redirecting is processed.
     */
    exitAfterCreate() {
        if (!this.returnAfterCreate && this.model.id) {
            let url = '#' + this.scope + '/view/' + this.model.id;

            this.getRouter().navigate(url, {trigger: false});

            this.getRouter().dispatch(this.scope, 'view', {
                id: this.model.id,
                rootUrl: this.options.rootUrl,
                model: this.model,
                isAfterCreate: true,
            });

            return true;
        }

        return false;
    }

    /**
     * Called after save or cancel. By default, redirects a page. Can be overridden in options.
     *
     * @param {string|'create'|'save'|'cancel'|'delete'} [after] Name of an action after which #exit is invoked.
     */
    exit(after) {
        if (after) {
            let methodName = 'exitAfter' + Espo.Utils.upperCaseFirst(after);

            if (methodName in this) {
                let result = this[methodName]();

                if (result) {
                    return;
                }
            }
        }

        let url;
        let options;

        if (this.returnUrl) {
            url = this.returnUrl;
        }
        else {
            if (after === 'delete') {
                url = this.options.rootUrl || '#' + this.scope;

                this.getRouter().navigate(url, {trigger: false});
                this.getRouter().dispatch(this.scope, null, {isReturn: true});

                return;
            }

            if (this.model.id) {
                url = '#' + this.scope + '/view/' + this.model.id;

                if (!this.returnDispatchParams) {
                    this.getRouter().navigate(url, {trigger: false});

                    options = {
                        id: this.model.id,
                        model: this.model,
                    };

                    if (this.options.rootUrl) {
                        options.rootUrl = this.options.rootUrl;
                    }

                    this.getRouter().dispatch(this.scope, 'view', options);
                }
            }
            else {
                url = this.options.rootUrl || '#' + this.scope;
            }
        }

        if (this.returnDispatchParams) {
            let controller = this.returnDispatchParams.controller;
            let action = this.returnDispatchParams.action;
            options = this.returnDispatchParams.options || {};

            this.getRouter().navigate(url, {trigger: false});
            this.getRouter().dispatch(controller, action, options);

            return;
        }

        this.getRouter().navigate(url, {trigger: true});
    }

    subscribeToWebSocket() {
        let topic = 'recordUpdate.' + this.entityType + '.' + this.model.id;

        this.recordUpdateWebSocketTopic = topic;
        this.isSubscribedToWebSocket = true;

        this.getHelper().webSocketManager.subscribe(topic, () => {
            this.handleRecordUpdate();
        });
    }

    unsubscribeFromWebSocket() {
        if (!this.isSubscribedToWebSocket) {
            return;
        }

        this.getHelper().webSocketManager.unsubscribe(this.recordUpdateWebSocketTopic);
    }

    handleRecordUpdate() {
        if (this.updateWebSocketIsBlocked) {
            return;
        }

        if (this.inlineEditModeIsOn || this.mode === this.MODE_EDIT) {
            let m = this.model.clone();

            m.fetch().then(() => {
                if (this.inlineEditModeIsOn || this.mode === this.MODE_EDIT) {
                    this.updatedAttributes = Espo.Utils.cloneDeep(m.attributes);
                }
            });

            return;
        }

        this.model.fetch({highlight: true});
    }

    blockUpdateWebSocket(toUnblock) {
        this.updateWebSocketIsBlocked = true;

        if (toUnblock) {
            setTimeout(() => {
                this.unblockUpdateWebSocket();
            }, this.blockUpdateWebSocketPeriod);
        }
    }

    unblockUpdateWebSocket() {
        this.updateWebSocketIsBlocked = false;
    }

    /**
     * Show more detail panels.
     */
    showMoreDetailPanels() {
        this.hidePanel('showMoreDelimiter');

        this.underShowMoreDetailPanelList.forEach(item => {
            this.showPanel(item);
        });
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * @protected
     * @return {Number}
     */
    getTabCount() {
        if (!this.hasTabs()) {
            return 0;
        }

        let count = 1;

        (this.detailLayout || []).forEach(item => {
            if (item.tabBreak) {
                count ++;
            }
        });

        return count;
    }

    /**
     * @protected
     * @return {boolean}
     */
    hasTabs() {
        if (typeof this._hasMiddleTabs !== 'undefined') {
            return this._hasMiddleTabs;
        }

        if (!this.detailLayout) {
            return false;
        }

        for (let item of this.detailLayout) {
            if (item.tabBreak) {
                this._hasMiddleTabs = true;

                return true;
            }
        }

        this._hasMiddleTabs = false;

        return false;
    }

    /**
     * @private
     * @return {{label: string}[]}
     */
    getMiddleTabDataList() {
        let currentTab = this.currentTab;

        let panelDataList = this.middlePanelDefsList;

        return panelDataList
            .filter((item, i) => i === 0 || item.tabBreak)
            .map((item, i) => {
                let label = item.tabLabel;

                let hidden = false;

                if (i > 0) {
                    hidden = panelDataList
                        .filter(panel => panel.tabNumber === i)
                        .findIndex(panel => !this.recordHelper.getPanelStateParam(panel.name, 'hidden')) === -1;
                }

                if (!label) {
                    label = i === 0 ?
                        this.translate('Overview') :
                        (i + 1).toString();
                }
                else if (label.substring(0, 7) === '$label:') {
                    label = this.translate(label.substring(7), 'labels', this.scope);
                }
                else if (label[0] === '$') {
                    label = this.translate(label.substring(1), 'tabs', this.scope);
                }

                return {
                    label: label,
                    isActive: currentTab === i,
                    hidden: hidden,
                };
            });
    }

    /**
     * Select a tab.
     *
     * @protected
     * @param {Number} tab
     */
    selectTab(tab) {
        this.currentTab = tab;

        $('.popover.in').removeClass('in');

        this.whenRendered().then(() => {
            this.$el.find('.middle-tabs > button').removeClass('active');
            this.$el.find(`.middle-tabs > button[data-tab="${tab}"]`).addClass('active');

            this.$el.find('.middle > .panel[data-tab]').addClass('tab-hidden');
            this.$el.find(`.middle > .panel[data-tab="${tab}"]`).removeClass('tab-hidden');

            this.adjustMiddlePanels();
            this.recordHelper.trigger('panel-show');
        });

        this.storeTab();
    }

    /**
     * @private
     */
    storeTab() {
        let key = 'tab_middle';
        let keyRecord = 'tab_middle_record';

        this.getSessionStorage().set(key, this.currentTab);
        this.getSessionStorage().set(keyRecord, this.entityType + '_' + this.model.id);
    }

    /**
     * @private
     */
    selectStoredTab() {
        let key = 'tab_middle';

        let tab = this.getSessionStorage().get(key);

        if (tab > 0) {
            this.selectTab(tab);
        }
    }

    /**
     * @private
     */
    isStoredTabForThisRecord() {
        let keyRecord = 'tab_middle_record';

        return this.getSessionStorage().get(keyRecord) === this.entityType + '_' + this.model.id;
    }

    /**
     * @inheritDoc
      */
    onInvalid(invalidFieldList) {
        if (!this.hasTabs()) {
            return;
        }

        let tabList = [];

        for (let field of invalidFieldList) {
            let view = this.getMiddleView().getFieldView(field);

            if (!view) {
                continue;
            }

            let tabString = view.$el
                .closest('.panel.tab-hidden')
                .attr('data-tab');

            let tab = parseInt(tabString);

            if (tabList.indexOf(tab) !== -1) {
                continue;
            }

            tabList.push(tab);
        }

        if (!tabList.length) {
            return;
        }

        let $tabs = this.$el.find('.middle-tabs');

        tabList.forEach(tab => {
            let $tab = $tabs.find(`> [data-tab="${tab.toString()}"]`);

            $tab.addClass('invalid');

            $tab.one('click', () => {
                $tab.removeClass('invalid');
            });
        })
    }

    /**
     * @private
     */
    controlTabVisibilityShow(tab) {
        if (!this.hasTabs() || tab === 0) {
            return;
        }

        if (this.isBeingRendered()) {
            this.once('after:render', () => this.controlTabVisibilityShow(tab));

            return;
        }

        this.$el.find(`.middle-tabs > [data-tab="${tab.toString()}"]`).removeClass('hidden');
    }

    /**
     * @private
     */
    controlTabVisibilityHide(tab) {
        if (!this.hasTabs() || tab === 0) {
            return;
        }

        if (this.isBeingRendered()) {
            this.once('after:render', () => this.controlTabVisibilityHide(tab));

            return;
        }

        let panelList = this.middlePanelDefsList.filter(panel => panel.tabNumber === tab);

        let allIsHidden = panelList
            .findIndex(panel => !this.recordHelper.getPanelStateParam(panel.name, 'hidden')) === -1;

        if (!allIsHidden) {
            return;
        }

        let $tab = this.$el.find(`.middle-tabs > [data-tab="${tab.toString()}"]`);

        $tab.addClass('hidden');

        if (this.currentTab === tab) {
            this.selectTab(0);
        }
    }

    /**
     * @private
     */
    adjustMiddlePanels() {
        if (!this.isRendered() || !this.$middle.length) {
            return;
        }

        let $panels = this.$middle.find('> .panel');
        let $bottomPanels = this.$bottom ? this.$bottom.find('> .panel') : null;

        $panels
            .removeClass('first')
            .removeClass('last')
            .removeClass('in-middle');

        let $visiblePanels = $panels.filter(`:not(.tab-hidden):not(.hidden)`)

        $visiblePanels.each((i, el) => {
            let $el = $(el);

            if (i === $visiblePanels.length - 1) {
                if ($bottomPanels && $bottomPanels.first().hasClass('sticked')) {
                    if (i === 0) {
                        $el.addClass('first');

                        return;
                    }

                    $el.addClass('in-middle');

                    return;
                }

                if (i === 0) {
                    return;
                }

                $el.addClass('last');

                return;
            }

            if (i > 0 && i < $visiblePanels.length - 1) {
                $el.addClass('in-middle');

                return;
            }

            if (i === 0) {
                $el.addClass('first');
            }
        });
    }

    /**
     * @private
     */
    adjustButtons() {
        let $buttons = this.$detailButtonContainer.filter('.record-buttons').find('button.btn');

        $buttons
            .removeClass('radius-left')
            .removeClass('radius-right');

        let $buttonsVisible = $buttons.filter('button:not(.hidden)');

        $buttonsVisible.first().addClass('radius-left');
        $buttonsVisible.last().addClass('radius-right');

        this.adjustEditButtons();
    }

    /**
     * @private
     */
    adjustEditButtons() {
        let $buttons = this.$detailButtonContainer.filter('.edit-buttons').find('button.btn');

        $buttons
            .removeClass('radius-left')
            .removeClass('radius-right');

        let $buttonsVisible = $buttons.filter('button:not(.hidden)');

        $buttonsVisible.first().addClass('radius-left');
        $buttonsVisible.last().addClass('radius-right');
    }

    /**
     * @private
     */
    initElementReferences() {
        if (this.$detailButtonContainer && this.$detailButtonContainer.length) {
            return;
        }

        this.$detailButtonContainer = this.$el.find('.detail-button-container');

        this.$dropdownItemListButton = this.$detailButtonContainer
            .find('.dropdown-item-list-button');

        this.$dropdownEditItemListButton = this.$detailButtonContainer
            .find('.dropdown-edit-item-list-button');
    }

    /**
     * @protected
     */
    focusForEdit() {
        this.$el
            .find('.field:not(.hidden) .form-control:not([disabled])')
            .first()
            .focus();
    }

    /**
     * @protected
     */
    focusForCreate() {
        this.$el
            .find('.form-control:not([disabled])')
            .first()
            .focus();
    }

    /**
     * @protected
     * @param {JQueryKeyEventObject} e
     */
    handleShortcutKeyCtrlEnter(e) {
        let action = this.shortcutKeyCtrlEnterAction;

        if (this.inlineEditModeIsOn || this.buttonsDisabled || !action) {
            return;
        }

        if (this.mode !== this.MODE_EDIT) {
            return;
        }

        if (!this.hasAvailableActionItem(action)) {
            return;
        }

        e.preventDefault();
        e.stopPropagation();

        let methodName = 'action' + Espo.Utils.upperCaseFirst(action);

        this[methodName]();
    }

    /**
     * @protected
     * @param {JQueryKeyEventObject} e
     */
    handleShortcutKeyCtrlS(e) {
        if (this.inlineEditModeIsOn || this.buttonsDisabled) {
            return;
        }

        e.preventDefault();
        e.stopPropagation();

        if (this.mode !== this.MODE_EDIT) {
            return;
        }

        if (!this.saveAndContinueEditingAction) {
            return;
        }

        if (!this.hasAvailableActionItem('saveAndContinueEditing')) {
            return;
        }

        this.actionSaveAndContinueEditing();
    }

    /**
     * @protected
     * @param {JQueryKeyEventObject} e
     */
    handleShortcutKeyCtrlSpace(e) {
        if (this.inlineEditModeIsOn || this.buttonsDisabled) {
            return;
        }

        if (this.type !== this.TYPE_DETAIL || this.mode !== this.MODE_DETAIL) {
            return;
        }

        if (e.target.tagName === 'TEXTAREA' || e.target.tagName === 'INPUT') {
            return;
        }

        if (!this.hasAvailableActionItem('edit')) {
            return;
        }

        $(e.currentTarget)

        e.preventDefault();
        e.stopPropagation();

        this.actionEdit();

        if (!this.editModeDisabled) {
            setTimeout(() => this.focusForEdit(), 200);
        }
    }

    /**
     * @protected
     * @param {JQueryKeyEventObject} e
     */
    handleShortcutKeyEscape(e) {
        if (this.inlineEditModeIsOn || this.buttonsDisabled) {
            return;
        }

        if (this.type !== this.TYPE_DETAIL || this.mode !== this.MODE_EDIT) {
            return;
        }

        e.preventDefault();
        e.stopPropagation();

        // Fetching a currently edited form element.
        this.model.set(this.fetch());

        if (this.isChanged) {
            this.confirm(this.translate('confirmLeaveOutMessage', 'messages'))
                .then(() => this.actionCancelEdit());

            return;
        }

        this.actionCancelEdit();
    }

    /**
     * @protected
     * @param {JQueryKeyEventObject} e
     */
    handleShortcutKeyCtrlAltEnter(e) {}

    /**
     * @public
     * @param {JQueryKeyEventObject} e
     */
    handleShortcutKeyControlBackslash(e) {
        if (!this.hasTabs()) {
            return;
        }

        let $buttons = this.$el.find('.middle-tabs > button:not(.hidden)');

        if ($buttons.length === 1) {
            return;
        }

        e.preventDefault();
        e.stopPropagation();

        let index = $buttons.toArray().findIndex(el => $(el).hasClass('active'));

        index++;

        if (index >= $buttons.length) {
            index = 0;
        }

        let $tab = $($buttons.get(index));

        let tab = parseInt($tab.attr('data-tab'));

        this.selectTab(tab);

        if (this.mode === this.MODE_EDIT) {
            setTimeout(() => {
                this.$middle
                    .find(`.panel[data-tab="${tab}"] .cell:not(.hidden)`)
                    .first()
                    .focus();
            }, 50);

            return;
        }

        this.$el
            .find(`.middle-tabs button[data-tab="${tab}"]`)
            .focus();
    }

    /**
     * @protected
     * @param {JQueryKeyEventObject} e
     */
    handleShortcutKeyControlArrowLeft(e) {
        if (this.inlineEditModeIsOn || this.buttonsDisabled) {
            return;
        }

        if (this.navigateButtonsDisabled) {
            return;
        }

        if (this.type !== this.TYPE_DETAIL || this.mode !== this.MODE_DETAIL) {
            return;
        }

        if (e.target.tagName === 'TEXTAREA' || e.target.tagName === 'INPUT') {
            return;
        }

        let $button = this.$el.find('button[data-action="previous"]');

        if (!$button.length || $button.hasClass('disabled')) {
            return;
        }

        e.preventDefault();
        e.stopPropagation();

        this.actionPrevious();
    }

    /**
     * @protected
     * @param {JQueryKeyEventObject} e
     */
    handleShortcutKeyControlArrowRight(e) {
        if (this.inlineEditModeIsOn || this.buttonsDisabled) {
            return;
        }

        if (this.navigateButtonsDisabled) {
            return;
        }

        if (this.type !== this.TYPE_DETAIL || this.mode !== this.MODE_DETAIL) {
            return;
        }

        if (e.target.tagName === 'TEXTAREA' || e.target.tagName === 'INPUT') {
            return;
        }

        let $button = this.$el.find('button[data-action="next"]');

        if (!$button.length || $button.hasClass('disabled')) {
            return;
        }

        e.preventDefault();
        e.stopPropagation();

        this.actionNext();
    }

    /**
     * Get a current mode.
     *
     * @since 8.0.0
     * @return {string}
     */
    getMode() {
        return this.mode;
    }
}

export default DetailRecordView;
PK]���
�
&views/record/list-nested-categories.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/record/list-nested-categories */

import View from 'view';

class ListNestedCategoriesRecordView extends View {

    template = 'record/list-nested-categories'

    isLoading = false

    events = {
        'click .action': function (e) {
            Espo.Utils.handleAction(this, e.originalEvent, e.currentTarget);
        },
    }

    data() {
        let data = {};

        if (!this.isLoading) {
            data.list = this.getDataList();
        }

        data.scope = this.collection.entityType;
        data.isLoading = this.isLoading;
        data.currentId = this.collection.currentCategoryId;
        data.currentName = this.collection.currentCategoryName;
        data.categoryData = this.collection.categoryData;

        data.hasExpandedToggler = this.options.hasExpandedToggler;
        data.showEditLink = this.options.showEditLink;
        data.hasNavigationPanel = this.options.hasNavigationPanel;

        let categoryData = this.collection.categoryData || {};

        data.upperLink = categoryData.upperId ?
            '#' + this.subjectEntityType + '/list/categoryId=' + categoryData.upperId:
            '#' + this.subjectEntityType;

        return data;
    }

    getDataList() {
        var list = [];

        this.collection.forEach(model => {
            let o = {
                id: model.id,
                name: model.get('name'),
                recordCount: model.get('recordCount'),
                isEmpty: model.get('isEmpty'),
                link: '#' + this.subjectEntityType + '/list/categoryId=' + model.id,
            };

            list.push(o);
        });

        return list;
    }

    setup() {
        this.listenTo(this.collection, 'sync', () => {
            this.reRender();
        });

        this.subjectEntityType = this.options.subjectEntityType;
    }

    actionShowMore() {
        this.$el.find('.category-item.show-more').addClass('hidden');

        this.collection.fetch({
            remove: false,
            more: true,
        });
    }
}

export default ListNestedCategoriesRecordView;
PK]�^##views/record/edit-side.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/record/edit-side */

import DetailSideRecordView from 'views/record/detail-side';

class EditSideRecordView extends DetailSideRecordView {

    /** @inheritDoc */
    mode = 'edit'

    /** @inheritDoc */
    defaultPanelDefs = {
        name: 'default',
        label: false,
        view: 'views/record/panels/side',
        isForm: true,
        options: {
            fieldList: [
                {
                    name: ':assignedUser'
                },
                {
                    name: 'teams',
                    view: 'views/fields/teams'
                }
            ]
        }
    }
}

export default EditSideRecordView;
PK]b�k
hxhxviews/record/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/record/list */

import View from 'view';
import MassActionHelper from 'helpers/mass-action';
import ExportHelper from 'helpers/export';
import RecordModal from 'helpers/record-modal';
import SelectProvider from 'helpers/list/select-provider';

/**
 * A record-list view. Renders and processes list items, actions.
 *
 * @todo Document all options.
 */
class ListRecordView extends View {

    /**
     * A row action.
     *
     * @typedef {Object} module:views/record/list~rowAction
     *
     * @property {string} action An action.
     * @property {string} [label] A label.
     * @property {string} [link] A link.
     * @property {Object.<string, string|number|boolean>} [data] Data attributes.
     */

    /** @inheritDoc */
    template = 'record/list'

    /**
     * A type. Can be 'list', 'listSmall'.
     */
    type = 'list'

    /** @inheritDoc */
    name = 'list'

    // noinspection JSUnusedGlobalSymbols
    /**
     * A presentation type.
     */
    presentationType = 'table'

    /**
     * If true checkboxes will be shown. Can be overridden by an option parameter.
     *
     * @protected
     */
    checkboxes = true

    /**
     * If true clicking on the record link will trigger 'select' event with model passed.
     * Can be overridden by an option parameter.
     */
    selectable = false

    /**
     * A row-actions view. A dropdown on the right side.
     *
     * @protected
     * @type {string}
     */
    rowActionsView = 'views/record/row-actions/default'

    /**
     * Disable row-actions. Can be overridden by an option parameter.
     */
    rowActionsDisabled = false

    /**
     * An entity type. Set automatically.
     *
     * @type {string|null}
     */
    entityType = null

    /**
     * A scope. Set automatically.
     *
     * @type {?string}
     */
    scope = null

    /**
     * @protected
     */
    _internalLayoutType = 'list-row'

    /**
     * A selector to a list container.
     *
     * @protected
     */
    listContainerEl = '.list > table > tbody'

    /**
     * To show number of records. Can be overridden by an option parameter.
     *
     * @protected
     */
    showCount = true

    /**
     * @protected
     */
    rowActionsColumnWidth = 25

    /**
     * @protected
     */
    checkboxColumnWidth = 40

    /**
     * @protected
     */
    minColumnWidth = 100

    /**
     * A button. Handled by a class method `action{Name}` or a handler.
     *
     * @typedef {Object} module:views/record/list~button
     *
     * @property {string} name A name.
     * @property {string} label A label. To be translated in a current scope.
     * @property {'default'|'danger'|'warning'|'success'} [style] A style.
     * @property {boolean} [hidden] Hidden.
     * @property {function()} [onClick] A click handler.
     */

    /**
     * A button list.
     *
     * @protected
     * @type {module:views/record/list~button[]}
     */
    buttonList = []

    /**
     * A dropdown item. Handled by a class method `action{Name}` or a handler.
     *
     * @typedef {Object} module:views/record/list~dropdownItem
     *
     * @property {string} name A name.
     * @property {string} [label] A label. To be translated in a current scope.
     * @property {string} [html] An HTML.
     * @property {boolean} [hidden] Hidden.
     * @property {function()} [onClick] A click handler.
     */

    /**
     * A dropdown item list. Can be overridden by an option parameter.
     *
     * @protected
     * @type {module:views/record/list~dropdownItem[]}
     */
    dropdownItemList = []

    /**
     * Disable a header. Can be overridden by an option parameter.
     *
     * @protected
     */
    headerDisabled = false

    /**
     * Disable mass actions. Can be overridden by an option parameter.
     *
     * @protected
     */
    massActionsDisabled = false

    /**
     * Disable a portal layout usage. Can be overridden by an option parameter.
     *
     * @protected
     */
    portalLayoutDisabled = false

    /**
     * Mandatory select attributes. Can be overridden by an option parameter.
     * Attributes to be selected regardless being on a layout.
     *
     * @protected
     * @type {string[]|null}
     */
    mandatorySelectAttributeList = null

    /**
     * A layout name. If null, a value from `type` property will be used.
     * Can be overridden by an option parameter.
     *
     * @protected
     * @type {string|null}
     */
    layoutName = null

    /**
     * A scope name for layout loading. If null, an entity type of collection will be used.
     * Can be overridden by an option parameter.
     *
     * @protected
     * @type {string|null}
     */
    layoutScope = null

    /**
     * To disable field-level access check for a layout.
     * Can be overridden by an option parameter.
     *
     * @protected
     */
    layoutAclDisabled = false

    /**
     * A setup-handler type.
     *
     * @protected
     */
    setupHandlerType = 'record/list'

    /**
     * @internal
     * @private
     */
    checkboxesDisabled = false

    /**
     * Force displaying the top bar even if empty. Can be overridden by an option parameter.
     * @protected
     */
    forceDisplayTopBar = false

    /**
     * Where to display the pagination. Can be overridden by an option parameter.
     *
     * @protected
     * @type {'top'|'bottom'|boolean|null}
     */
    pagination = false

    /**
     * To display a table header with column names. Can be overridden by an option parameter.
     *
     * @protected
     * @type {boolean}
     */
    header = true

    /**
     * A show-more button. Can be overridden by an option parameter.
     *
     * @protected
     */
    showMore = true

    /**
     * A mass-action list.
     *
     * @protected
     * @type {string[]}
     */
    massActionList = [
        'remove',
        'merge',
        'massUpdate',
        'export',
    ]

    /**
     * A mass-action list available when selecting all results.
     *
     * @protected
     * @type {string[]}
     */
    checkAllResultMassActionList = [
        'remove',
        'massUpdate',
        'export',
    ]

    /**
     * A forced mass-action list.
     *
     * @protected
     * @type {?string[]}
     */
    forcedCheckAllResultMassActionList = null

    /**
     * Disable quick-detail (viewing a record in modal)
     *
     * @protected
     */
    quickDetailDisabled = false

    /**
     * Disable quick-edit (editing a record in modal)
     *
     * @protected
     */
    quickEditDisabled = false

    /**
     * Column definitions.
     *
     * @typedef module:views/record/list~columnDefs
     * @type {Object}
     * @property {string} name A name (usually a field name).
     * @property {string} [view] An overridden field view name.
     * @property {number} [width] A width in percents.
     * @property {number} [widthPx] A width in pixels.
     * @property {boolean} [link] To use `listLink` mode (link to the detail view).
     * @property {'left'|'right'} [align] An alignment.
     * @property {string} [type] An overridden field type.
     * @property {Object.<string, *>} [params] Overridden field parameters.
     * @property {Object.<string, *>} [options] Field view options.
     */

    /**
     * A list layout. Can be overridden by an option parameter.
     * If null, then will be loaded from the backend (using the `layoutName` value).
     *
     * @protected
     * @type {module:views/record/list~columnDefs[]|null}
     */
    listLayout = null

    /**
     * @private
     */
    _internalLayout = null

    /**
     * A list of record IDs currently selected. Only for reading.
     *
     * @protected
     * @type {string[]|null}
     */
    checkedList = null

    /**
     * Whether all results currently selected. Only for reading.
     *
     * @protected
     */
    allResultIsChecked = false

    /**
     * Disable the ability to select all results. Can be overridden by an option parameter.
     *
     * @protected
     */
    checkAllResultDisabled = false

    /**
     * Disable buttons. Can be overridden by an option parameter.
     *
     * @protected
     */
    buttonsDisabled = false

    /**
     * Disable edit. Can be overridden by an option parameter.
     *
     * @protected
     */
    editDisabled = false

    /**
     * Disable remove. Can be overridden by an option parameter.
     *
     * @protected
     */
    removeDisabled = false

    /**
     * Disable a stick-bar. Can be overridden by an option parameter.
     *
     * @protected
     */
    stickedBarDisabled = false

    /**
     * Disable the follow/unfollow mass action.
     *
     * @protected
     */
    massFollowDisabled = false

    /**
     * Disable the print-pdf mass action.
     *
     * @protected
     */
    massPrintPdfDisabled = false

    /**
     * Disable the convert-currency mass action.
     *
     * @protected
     */
    massConvertCurrencyDisabled = false

    /**
     * Disable mass-update.
     *
     * @protected
     */
    massUpdateDisabled = false

    /**
     * Disable export.
     *
     * @protected
     */
    exportDisabled = false

    /**
     * Disable merge.
     *
     * @protected
     */
    mergeDisabled = false

    /**
     * Disable a no-data label (when no result).
     *
     * @protected
     */
    noDataDisabled = false

    /**
     * @private
     */
    _$focusedCheckbox = null

    /**
     * @protected
     * @type {?JQuery}
     */
    $selectAllCheckbox = null

    /**
     * @protected
     * @type {?Object.<string, Object.<string, *>>}
     */
    massActionDefs = null

    /** @inheritDoc */
    events = {
        /**
         * @param {JQueryKeyEventObject} e
         * @this ListRecordView
         */
        'click a.link': function (e) {
            if (e.ctrlKey || e.metaKey || e.shiftKey) {
                return;
            }

            e.stopPropagation();

            if (!this.scope || this.selectable) {
                return;
            }

            e.preventDefault();

            const id = $(e.currentTarget).attr('data-id');
            const model = this.collection.get(id);
            const scope = this.getModelScope(id);

            const options = {
                id: id,
                model: model,
            };

            if (this.options.keepCurrentRootUrl) {
                options.rootUrl = this.getRouter().getCurrentUrl();
            }

            this.getRouter().navigate('#' + scope + '/view/' + id, {trigger: false});
            this.getRouter().dispatch(scope, 'view', options);
        },
        /**
         * @param {JQueryMouseEventObject} e
         * @this ListRecordView
         */
        'auxclick a.link': function (e) {
            const isCombination = e.button === 1 && (e.ctrlKey || e.metaKey);

            if (!isCombination) {
                return;
            }

            const $target = $(e.currentTarget);

            const id = $target.attr('data-id');

            if (!id) {
                return;
            }

            if (this.quickDetailDisabled) {
                return;
            }

            const $menu = $target.parent().closest(`[data-id="${id}"]`)
                .find(`ul.list-row-dropdown-menu[data-id="${id}"]`);

            const $quickView = $menu.find(`a[data-action="quickView"]`);

            if ($menu.length && !$quickView.length) {
                return;
            }

            e.preventDefault();
            e.stopPropagation();

            this.actionQuickView({id: id});
        },
        /** @this ListRecordView */
        'click [data-action="showMore"]': function () {
            this.showMoreRecords();
        },
        'mousedown a.sort': function (e) {
            e.preventDefault();
        },
        /**
         * @param {JQueryKeyEventObject} e
         * @this module:views/record/list
         */
        'click a.sort': function (e) {
            const field = $(e.currentTarget).data('name');

            this.toggleSort(field);
        },
        /**
         * @param {JQueryKeyEventObject} e
         * @this ListRecordView
         */
        'click .pagination a': function (e) {
            const page = $(e.currentTarget).data('page');

            if ($(e.currentTarget).parent().hasClass('disabled')) {
                return;
            }

            Espo.Ui.notify(' ... ');

            this.collection.once('sync', () => {
                Espo.Ui.notify(false);
            });

            if (page === 'current') {
                this.collection.fetch();
            }
            else {
                this.collection[page + 'Page'].call(this.collection);
                this.trigger('paginate');
            }

            this.deactivate();
        },
        /** @this ListRecordView */
        'mousedown input.record-checkbox': function () {
            const $focused = $(document.activeElement);

            this._$focusedCheckbox = null;

            if (
                $focused.length &&
                $focused.get(0).tagName === 'INPUT' &&
                $focused.hasClass('record-checkbox')
            ) {
                this._$focusedCheckbox = $focused;
            }
        },
        /**
         * @param {JQueryKeyEventObject} e
         * @this ListRecordView
         */
        'click input.record-checkbox': function (e) {
            const $target = $(e.currentTarget);

            const $from = this._$focusedCheckbox;

            if (e.shiftKey && $from) {
                const $checkboxes = this.$el.find('input.record-checkbox');
                const start = $checkboxes.index($target);
                const end = $checkboxes.index($from);
                const checked = $from.prop('checked');

                $checkboxes.slice(Math.min(start, end), Math.max(start, end) + 1).each((i, el) => {
                    const $el = $(el);

                    $el.prop('checked', checked);
                    this.checkboxClick($el, checked);
                });

                return;
            }

            this.checkboxClick($target, $target.is(':checked'));
        },
        /**
         * @param {JQueryKeyEventObject} e
         * @this module:views/record/list
         */
        'click .select-all': function (e) {
            // noinspection JSUnresolvedReference
            this.selectAllHandler(e.currentTarget.checked);
        },
        /** @this ListRecordView */
        'click .action': function (e) {
            Espo.Utils.handleAction(this, e.originalEvent, e.currentTarget, {
                actionItems: [...this.buttonList, ...this.dropdownItemList],
                className: 'list-action-item',
            });
        },
        /** @this ListRecordView */
        'click .checkbox-dropdown [data-action="selectAllResult"]': function () {
            this.selectAllResult();
        },
        /**
         * @param {JQueryKeyEventObject} e
         * @this ListRecordView
         */
        'click .actions-menu a.mass-action': function (e) {
            const $el = $(e.currentTarget);

            const action = $el.data('action');
            const method = 'massAction' + Espo.Utils.upperCaseFirst(action);

            e.preventDefault();
            e.stopPropagation();

            const $parent = $el.closest('.dropdown-menu').parent();

            // noinspection JSUnresolvedReference
            $parent.find('.actions-button[data-toggle="dropdown"]')
                .dropdown('toggle')
                .focus();

            if (method in this) {
                this[method]();

                return;
            }

            this.massAction(action);
        },
        /** @this ListRecordView */
        'click a.reset-custom-order': function () {
            this.resetCustomOrder();
        },
    }

    /**
     * @param {JQuery} $checkbox
     * @param {boolean} checked
     * @private
     */
    checkboxClick($checkbox, checked) {
        const id = $checkbox.attr('data-id');

        if (checked) {
            this.checkRecord(id, $checkbox);

            return;
        }

        this.uncheckRecord(id, $checkbox);
    }

    resetCustomOrder() {
        this.collection.resetOrderToDefault();
        this.collection.trigger('order-changed');

        this.collection
            .fetch()
            .then(() => {
                this.trigger('sort', {
                    orderBy: this.collection.orderBy,
                    order: this.collection.order,
                });
            })
    }

    /**
     * @param {string} orderBy
     * @protected
     */
    toggleSort(orderBy) {
        let asc = true;

        if (orderBy === this.collection.orderBy && this.collection.order === 'asc') {
            asc = false;
        }

        const order = asc ? 'asc' : 'desc';

        Espo.Ui.notify(' ... ');

        const maxSizeLimit = this.getConfig().get('recordListMaxSizeLimit') || 200;

        while (this.collection.length > maxSizeLimit) {
            this.collection.pop();
        }

        this.collection
            .sort(orderBy, order)
            .then(() => {
                Espo.Ui.notify(false);

                this.trigger('sort', {orderBy: orderBy, order: order});
            })

        this.collection.trigger('order-changed');

        this.deactivate();
    }

    /**
     * @protected
     */
    initStickedBar() {
        const controlSticking = () => {
            if (this.checkedList.length === 0 && !this.allResultIsChecked) {
                return;
            }

            const scrollTop = $scrollable.scrollTop();

            const stickTop = buttonsTop;
            const edge = middleTop + $middle.outerHeight(true);

            if (isSmallWindow && $('#navbar .navbar-body').hasClass('in')) {
                return;
            }

            if (scrollTop >= edge) {
                $stickedBar.removeClass('hidden');
                $navbarRight.addClass('has-sticked-bar');

                return;
            }

            if (scrollTop > stickTop) {
                $stickedBar.removeClass('hidden');
                $navbarRight.addClass('has-sticked-bar');

                return;
            }

            $stickedBar.addClass('hidden');
            $navbarRight.removeClass('has-sticked-bar');
        };

        const $stickedBar = this.$stickedBar = this.$el.find('.sticked-bar');
        const $middle = this.$el.find('> .list');

        const $window = $(window);

        let $scrollable = $window;
        let $navbarRight = $('#navbar .navbar-right');

        this.on('render', () => {
            this.$stickedBar = null;
        });

        const isModal = !!this.$el.closest('.modal-body').length;

        const screenWidthXs = this.getThemeManager().getParam('screenWidthXs');
        const navbarHeight = this.getThemeManager().getParam('navbarHeight');

        const isSmallWindow = $(window.document).width() < screenWidthXs;

        const getOffsetTop = (element) => {
            let offsetTop = 0;

            const withHeader = !isSmallWindow && !isModal;

            do {
                if (element.classList.contains('modal-body')) {
                    break;
                }

                if (!isNaN(element.offsetTop)) {
                    offsetTop += element.offsetTop;
                }

                element = element.offsetParent;
            } while (element);

            if (withHeader) {
                offsetTop -= navbarHeight;
            }

            return offsetTop;
        };

        if (isModal) {
            $scrollable = this.$el.closest('.modal-body');
            $navbarRight = $scrollable.parent().find('.modal-footer');
        }

        let middleTop = getOffsetTop($middle.get(0));
        let buttonsTop =  getOffsetTop(this.$el.find('.list-buttons-container').get(0));

        if (!isModal) {
            // padding
            middleTop -= 5;
            buttonsTop -= 5;
        }

        $scrollable.off('scroll.list-' + this.cid);
        $scrollable.on('scroll.list-' + this.cid, () => controlSticking());

        $window.off('resize.list-' + this.cid);
        $window.on('resize.list-' + this.cid, () => controlSticking());

        this.on('check', () => {
            if (this.checkedList.length === 0 && !this.allResultIsChecked) {
                return;
            }

            controlSticking();
        });

        this.on('remove', () => {
            $scrollable.off('scroll.list-' + this.cid);
            $window.off('resize.list-' + this.cid);
        });
    }

    /**
     * @protected
     */
    showActions() {
        this.$el.find('.actions-button').removeClass('hidden');

        if (
            !this.options.stickedBarDisabled &&
            !this.stickedBarDisabled &&
            this.massActionList.length
        ) {
            if (!this.$stickedBar) {
                this.initStickedBar();
            }
        }
    }

    /**
     * @protected
     */
    hideActions() {
        this.$el.find('.actions-button').addClass('hidden');

        if (this.$stickedBar) {
            this.$stickedBar.addClass('hidden');
        }
    }

    /**
     * @protected
     */
    selectAllHandler(isChecked) {
        this.checkedList = [];

        if (isChecked) {
            this.$el.find('input.record-checkbox').prop('checked', true);

            this.showActions();

            this.collection.models.forEach((model) => {
                this.checkedList.push(model.id);
            });

            this.$el.find('.list > table tbody tr').addClass('active');
        }
        else {
            if (this.allResultIsChecked) {
                this.unselectAllResult();
            }

            this.$el.find('input.record-checkbox').prop('checked', false);
            this.hideActions();
            this.$el.find('.list > table tbody tr').removeClass('active');
        }

        this.trigger('check');
    }

    /** @inheritDoc */
    data() {
        const paginationTop = this.pagination === 'both' ||
            this.pagination === 'top';

        const paginationBottom = this.pagination === 'both' ||
            this.pagination === true ||
            this.pagination === 'bottom';

        const moreCount = this.collection.total - this.collection.length;
        let checkAllResultDisabled = this.checkAllResultDisabled;

        if (!this.massActionsDisabled) {
            if (!this.checkAllResultMassActionList.length) {
                checkAllResultDisabled = true;
            }
        }

        const displayTotalCount = this.displayTotalCount && this.collection.total > 0;

        const topBar =
            paginationTop ||
            this.checkboxes ||
            (this.buttonList.length && !this.buttonsDisabled) ||
            (this.dropdownItemList.length && !this.buttonsDisabled) ||
            this.forceDisplayTopBar ||
            displayTotalCount;

        const noDataDisabled = this.noDataDisabled || this._renderEmpty;

        return {
            scope: this.scope,
            entityType: this.entityType,
            header: this.header,
            headerDefs: this._getHeaderDefs(),
            paginationEnabled: this.pagination,
            paginationTop: paginationTop,
            paginationBottom: paginationBottom,
            showMoreActive: this.collection.hasMore(),
            showMoreEnabled: this.showMore,
            showCount: this.showCount && this.collection.total > 0,
            moreCount: moreCount,
            checkboxes: this.checkboxes,
            massActionList: this.massActionList,
            rowList: this.rowList,
            topBar: topBar,
            bottomBar: paginationBottom,
            checkAllResultDisabled: checkAllResultDisabled,
            buttonList: this.buttonList,
            dropdownItemList: this.dropdownItemList,
            displayTotalCount: displayTotalCount,
            displayActionsButtonGroup: this.checkboxes ||
                this.massActionList || this.buttonList.length || this.dropdownItemList.length,
            totalCountFormatted: this.getNumberUtil().formatInt(this.collection.total),
            moreCountFormatted: this.getNumberUtil().formatInt(moreCount),
            checkboxColumnWidth: this.checkboxColumnWidth,
            noDataDisabled: noDataDisabled,
        };
    }

    /** @inheritDoc */
    init() {
        this.type = this.options.type || this.type;
        this.listLayout = this.options.listLayout || this.listLayout;
        this.layoutName = this.options.layoutName || this.layoutName || this.type;
        this.layoutScope = this.options.layoutScope || this.layoutScope;
        this.layoutAclDisabled = this.options.layoutAclDisabled || this.layoutAclDisabled;
        this.headerDisabled = this.options.headerDisabled || this.headerDisabled;
        this.noDataDisabled = this.options.noDataDisabled || this.noDataDisabled;

        if (!this.headerDisabled) {
            this.header = _.isUndefined(this.options.header) ? this.header : this.options.header;
        } else {
            this.header = false;
        }

        this.pagination = _.isUndefined(this.options.pagination) || this.options.pagination === null ?
            this.pagination :
            this.options.pagination;

        this.checkboxes = _.isUndefined(this.options.checkboxes) ? this.checkboxes :
            this.options.checkboxes;
        this.selectable = _.isUndefined(this.options.selectable) ? this.selectable :
            this.options.selectable;

        this.checkboxesDisabled = this.options.checkboxes === false;

        this.rowActionsView = _.isUndefined(this.options.rowActionsView) ?
            this.rowActionsView :
            this.options.rowActionsView;

        this.showMore = _.isUndefined(this.options.showMore) ? this.showMore : this.options.showMore;

        this.massActionsDisabled = this.options.massActionsDisabled || this.massActionsDisabled;
        this.portalLayoutDisabled = this.options.portalLayoutDisabled || this.portalLayoutDisabled;

        if (this.massActionsDisabled && !this.selectable) {
            this.checkboxes = false;
        }

        this.rowActionsDisabled = this.options.rowActionsDisabled || this.rowActionsDisabled;

        this.dropdownItemList = Espo.Utils.cloneDeep(
            this.options.dropdownItemList || this.dropdownItemList);

        if ('buttonsDisabled' in this.options) {
            this.buttonsDisabled = this.options.buttonsDisabled;
        }

        if ('checkAllResultDisabled' in this.options) {
            this.checkAllResultDisabled = this.options.checkAllResultDisabled;
        }
    }

    /**
     * Get a record entity type (scope).
     *
     * @param {string} id A record ID.
     * @return {string}
     */
    getModelScope(id) {
        return this.scope;
    }

    /**
     * Select all results.
     */
    selectAllResult() {
        this.allResultIsChecked = true;

        this.hideActions();

        this.$el.find('input.record-checkbox').prop('checked', true).attr('disabled', 'disabled');
        this.$selectAllCheckbox.prop('checked', true);

        this.massActionList.forEach(item => {
            if (!~this.checkAllResultMassActionList.indexOf(item)) {
                this.$el
                    .find(
                        'div.list-buttons-container .actions-menu li a.mass-action[data-action="'+item+'"]'
                    )
                    .parent()
                    .addClass('hidden');
            }
        });

        if (this.checkAllResultMassActionList.length) {
            this.showActions();
        }

        this.$el.find('.list > table tbody tr').removeClass('active');

        this.trigger('select-all-results');
    }

    /**
     * Unselect all results.
     */
    unselectAllResult() {
        this.allResultIsChecked = false;

        this.$el.find('input.record-checkbox').prop('checked', false).removeAttr('disabled');
        this.$selectAllCheckbox.prop('checked', false);

        this.massActionList.forEach(item => {
            if (!~this.checkAllResultMassActionList.indexOf(item)) {
                this.$el
                    .find('div.list-buttons-container .actions-menu ' +
                        'li a.mass-action[data-action="'+item+'"]')
                    .parent()
                    .removeClass('hidden');
            }
        });
    }

    /**
     * @protected
     */
    deactivate() {
        if (this.$el) {
            this.$el.find(".pagination li").addClass('disabled');
            this.$el.find("a.sort").addClass('disabled');
        }
    }

    /**
     * Process export.
     *
     * @param {Object<string,*>} [data]
     * @param {string} [url='Export'] An API URL.
     * @param {string[]} [fieldList] A field list.
     */
    export(data, url, fieldList) {
        if (!data) {
            data = {
                entityType: this.entityType,
            };

            if (this.allResultIsChecked) {
                data.where = this.collection.getWhere();
                data.searchParams = this.collection.data || null;
                data.searchData = this.collection.data || {}; // for bc;
            }
            else {
                data.ids = this.checkedList;
            }
        }

        url = url || 'Export';

        const o = {
            scope: this.entityType,
        };

        if (fieldList) {
            o.fieldList = fieldList;
        }
        else {
            const layoutFieldList = [];

            (this.listLayout || []).forEach((item) => {
                if (item.name) {
                    layoutFieldList.push(item.name);
                }
            });

            o.fieldList = layoutFieldList;
        }

        const helper = new ExportHelper(this);
        const idle = this.allResultIsChecked && helper.checkIsIdle(this.collection.total);

        const proceedDownload = (attachmentId) => {
            window.location = this.getBasePath() + '?entryPoint=download&id=' + attachmentId;
        };

        this.createView('dialogExport', 'views/export/modals/export', o, (view) => {
            view.render();

            this.listenToOnce(view, 'proceed', (dialogData) => {
                if (!dialogData.exportAllFields) {
                    data.attributeList = dialogData.attributeList;
                    data.fieldList = dialogData.fieldList;
                }

                data.idle = idle;
                data.format = dialogData.format;
                data.params = dialogData.params;

                Espo.Ui.notify(this.translate('pleaseWait', 'messages'));

                Espo.Ajax
                    .postRequest(url, data, {timeout: 0})
                    .then(/** Object.<string, *> */response => {
                        Espo.Ui.notify(false);

                        if (response.exportId) {
                            helper
                                .process(response.exportId)
                                .then(view => {
                                    this.listenToOnce(view, 'download', id => {
                                        proceedDownload(id);
                                    });
                                });

                            return;
                        }

                        if (!response.id) {
                            throw new Error("No attachment-id.");
                        }

                        window.location = this.getBasePath() + '?entryPoint=download&id=' + response.id;

                        proceedDownload(response.id);
                    });
            });
        });
    }

    /**
     * Process a mass-action.
     *
     * @param {string} name An action.
     */
    massAction(name) {
        const defs = this.massActionDefs[name] || {};

        const handler = defs.handler;

        if (handler) {
            const method = 'action' + Espo.Utils.upperCaseFirst(name);

            const data = {
                entityType: this.entityType,
                action: name,
                params: this.getMassActionSelectionPostData(),
            };

            Espo.loader.require(handler, Handler => {
                const handler = new Handler(this);

                handler[method].call(handler, data);
            });

            return;
        }

        const bypassConfirmation = defs.bypassConfirmation || false;
        const confirmationMsg = defs.confirmationMessage || 'confirmation';
        const acl = defs.acl;
        const aclScope = defs.aclScope;

        const proceed = () => {
            if (acl || aclScope) {
                if (!this.getAcl().check(aclScope || this.scope, acl)) {
                    Espo.Ui.error(this.translate('Access denied'));

                    return;
                }
            }

            const idList = [];
            const data = {};

            if (this.allResultIsChecked) {
                data.where = this.collection.getWhere();
                data.searchParams = this.collection.data || {};
                data.selectData = data.searchData; // for bc;
                data.byWhere = true; // for bc
            } else {
                data.idList = idList; // for bc
                data.ids = idList;
            }

            for (const i in this.checkedList) {
                idList.push(this.checkedList[i]);
            }

            data.entityType = this.entityType;

            const waitMessage = defs.waitMessage || 'pleaseWait';

            Espo.Ui.notify(this.translate(waitMessage, 'messages', this.scope));

            const url = defs.url;

            Espo.Ajax.postRequest(url, data)
                .then(/** Object.<string, *> */result => {
                    const successMessage = result.successMessage || defs.successMessage || 'done';

                    this.collection
                        .fetch()
                        .then(() => {
                            let message = this.translate(successMessage, 'messages', this.scope);

                            if ('count' in result) {
                                message = message.replace('{count}', result.count);
                            }

                            Espo.Ui.success(message);
                        });
                });
        };

        if (!bypassConfirmation) {
            this.confirm(this.translate(confirmationMsg, 'messages', this.scope), proceed, this);
        }
        else {
            proceed.call(this);
        }
    }

    getMassActionSelectionPostData() {
        const data = {};

        if (this.allResultIsChecked) {
            data.where = this.collection.getWhere();
            data.searchParams = this.collection.data || {};
            data.selectData = this.collection.data || {}; // for bc;
            data.byWhere = true; // for bc;
        }
        else {
            data.ids = [];

            for (const i in this.checkedList) {
                data.ids.push(this.checkedList[i]);
            }
        }

        return data;
    }

    // noinspection JSUnusedGlobalSymbols
    massActionRecalculateFormula() {
        let ids = false;

        const allResultIsChecked = this.allResultIsChecked;

        if (!allResultIsChecked) {
            ids = this.checkedList;
        }

        this.confirm({
            message: this.translate('recalculateFormulaConfirmation', 'messages'),
            confirmText: this.translate('Yes'),
        }, () => {
            Espo.Ui.notify(this.translate('pleaseWait', 'messages'));

            const params = this.getMassActionSelectionPostData();
            const helper = new MassActionHelper(this);
            const idle = !!params.searchParams && helper.checkIsIdle(this.collection.total);

            Espo.Ajax.postRequest('MassAction', {
                entityType: this.entityType,
                action: 'recalculateFormula',
                params: params,
                idle: idle,
            })
                .then(result => {
                    result = result || {};

                    const final = () => {
                        this.collection
                            .fetch()
                            .then(() => {
                                Espo.Ui.success(this.translate('Done'));

                                if (allResultIsChecked) {
                                    this.selectAllResult();

                                    return;
                                }

                                ids.forEach((id) => {
                                    this.checkRecord(id);
                                });
                            });
                    };

                    if (result.id) {
                        helper
                            .process(result.id, 'recalculateFormula')
                            .then(view => {
                                this.listenToOnce(view, 'close:success', () => final());
                            });

                        return;
                    }

                    final();
                });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    massActionRemove() {
        if (!this.getAcl().check(this.entityType, 'delete')) {
            Espo.Ui.error(this.translate('Access denied'));

            return false;
        }

        this.confirm({
            message: this.translate('removeSelectedRecordsConfirmation', 'messages', this.scope),
            confirmText: this.translate('Remove'),
        }, () => {
            Espo.Ui.notify(' ... ');

            const helper = new MassActionHelper(this);
            const params = this.getMassActionSelectionPostData();
            const idle = !!params.searchParams && helper.checkIsIdle(this.collection.total);

            Espo.Ajax.postRequest('MassAction', {
                entityType: this.entityType,
                action: 'delete',
                params: params,
                idle: idle,
            })
            .then(result => {
                result = result || {};

                const afterAllResult = count => {
                    if (!count) {
                        Espo.Ui.warning(this.translate('noRecordsRemoved', 'messages'));

                        return;
                    }

                    this.unselectAllResult();

                    this.collection
                        .fetch()
                        .then(() => {
                            const msg = count === 1 ? 'massRemoveResultSingle' : 'massRemoveResult';

                            Espo.Ui.success(this.translate(msg, 'messages').replace('{count}', count));
                        });

                    this.collection.trigger('after:mass-remove');

                    Espo.Ui.notify(false);
                };

                if (result.id) {
                    helper
                        .process(result.id, 'delete')
                        .then(view => {
                            this.listenToOnce(view, 'close:success', result => afterAllResult(result.count));
                        });

                    return;
                }

                const count = result.count;

                if (this.allResultIsChecked) {
                    afterAllResult(count);

                    return;
                }

                const idsRemoved = result.ids || [];

                if (!count) {
                    Espo.Ui.warning(this.translate('noRecordsRemoved', 'messages'));

                    return;
                }

                idsRemoved.forEach(id => {
                    Espo.Ui.notify(false);

                    this.collection.trigger('model-removing', id);
                    this.removeRecordFromList(id);
                    this.uncheckRecord(id, null, true);
                });

                if (this.$selectAllCheckbox.prop('checked')) {
                    this.$selectAllCheckbox.prop('checked', false);

                    if (this.collection.hasMore()) {
                        this.showMoreRecords({skipNotify: true});
                    }
                }

                this.collection.trigger('after:mass-remove');

                const msg = count === 1 ? 'massRemoveResultSingle' : 'massRemoveResult';

                Espo.Ui.success(this.translate(msg, 'messages').replace('{count}', count));
            });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    massActionPrintPdf() {
        const maxCount = this.getConfig().get('massPrintPdfMaxCount');

        if (maxCount) {
            if (this.checkedList.length > maxCount) {
                const msg = this.translate('massPrintPdfMaxCountError', 'messages')
                    .replace('{maxCount}', maxCount.toString());

                Espo.Ui.error(msg);

                return;
            }
        }

        const idList = [];

        for (const i in this.checkedList) {
            idList.push(this.checkedList[i]);
        }

        this.createView('pdfTemplate', 'views/modals/select-template', {
            entityType: this.entityType,
        }, view => {
            view.render();

            this.listenToOnce(view, 'select', (templateModel) => {
                this.clearView('pdfTemplate');

                Espo.Ui.notify(' ... ');

                Espo.Ajax.postRequest(
                    'Pdf/action/massPrint',
                    {
                        idList: idList,
                        entityType: this.entityType,
                        templateId: templateModel.id,
                    },
                    {timeout: 0}
                ).then(result => {
                    Espo.Ui.notify(false);

                    window.open('?entryPoint=download&id=' + result.id, '_blank');
                });
            });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    massActionFollow() {
        const count = this.checkedList.length;

        const confirmMsg = this.translate('confirmMassFollow', 'messages')
            .replace('{count}', count.toString());

        this.confirm({
            message: confirmMsg,
            confirmText: this.translate('Follow'),
        }, () => {
            Espo.Ui.notify(this.translate('pleaseWait', 'messages'));

            Espo.Ajax
                .postRequest('MassAction', {
                    action: 'follow',
                    entityType: this.entityType,
                    params: this.getMassActionSelectionPostData(),
                })
                .then(result => {
                    const resultCount = result.count || 0;

                    let msg = 'massFollowResult';

                    if (resultCount) {
                        if (resultCount === 1) {
                            msg += 'Single';
                        }

                        Espo.Ui.success(
                            this.translate(msg, 'messages').replace('{count}', resultCount.toString())
                        );

                        return;
                    }

                    Espo.Ui.warning(
                        this.translate('massFollowZeroResult', 'messages')
                    );
                });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    massActionUnfollow() {
        const count = this.checkedList.length;

        const confirmMsg = this.translate('confirmMassUnfollow', 'messages')
            .replace('{count}', count.toString());

        this.confirm({
            message: confirmMsg,
            confirmText: this.translate('Unfollow'),
        }, () => {
            Espo.Ui.notify(this.translate('pleaseWait', 'messages'));

            const params = this.getMassActionSelectionPostData();
            const helper = new MassActionHelper(this);
            const idle = !!params.searchParams && helper.checkIsIdle(this.collection.total);

            Espo.Ajax
                .postRequest('MassAction', {
                    action: 'unfollow',
                    entityType: this.entityType,
                    params: params,
                    idle: idle,
                })
                .then(result => {
                    const final = (count) => {
                        let msg = 'massUnfollowResult';

                        if (!count) {
                            Espo.Ui.warning(
                                this.translate('massUnfollowZeroResult', 'messages')
                            );
                        }

                        if (count === 1) {
                            msg += 'Single';
                        }

                        Espo.Ui.success(
                            this.translate(msg, 'messages').replace('{count}', count)
                        );
                    };

                    if (result.id) {
                        helper
                            .process(result.id, 'unfollow')
                            .then(view => {
                                this.listenToOnce(view, 'close:success', result => final(result.count));
                            });

                        return;
                    }

                    final(result.count || 0);
                });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    massActionMerge() {
        if (!this.getAcl().check(this.entityType, 'edit')) {
            Espo.Ui.error(this.translate('Access denied'));

            return false;
        }

        if (this.checkedList.length < 2) {
            Espo.Ui.error(this.translate('Select 2 or more records'));

            return;
        }
        if (this.checkedList.length > 4) {
            Espo.Ui.error(this.translate('Select not more than 4 records'));

            return;
        }

        this.checkedList.sort();

        const url = '#' + this.entityType + '/merge/ids=' + this.checkedList.join(',');

        this.getRouter().navigate(url, {trigger: false});

        this.getRouter().dispatch(this.entityType, 'merge', {
            ids: this.checkedList.join(','),
            collection: this.collection,
        });
    }

    // noinspection JSUnusedGlobalSymbols
    massActionMassUpdate() {
        if (!this.getAcl().check(this.entityType, 'edit')) {
            Espo.Ui.error(this.translate('Access denied'));

            return false;
        }

        Espo.Ui.notify(' ... ');

        let ids = false;

        const allResultIsChecked = this.allResultIsChecked;

        if (!allResultIsChecked) {
            ids = this.checkedList;
        }

        const viewName = this.getMetadata().get(['clientDefs', this.entityType, 'modalViews', 'massUpdate']) ||
            'views/modals/mass-update';

        this.createView('massUpdate', viewName, {
            scope: this.scope,
            entityType: this.entityType,
            ids: ids,
            where: this.collection.getWhere(),
            searchParams: this.collection.data,
            byWhere: this.allResultIsChecked,
            totalCount: this.collection.total,
        }, view => {
            view.render();

            view.notify(false);

            this.listenToOnce(view, 'after:update', (o) => {
                if (o.idle) {
                    this.listenToOnce(view, 'close', () => {
                        this.collection
                            .fetch()
                            .then(() => {
                                if (allResultIsChecked) {
                                    this.selectAllResult();

                                    return;
                                }

                                ids.forEach((id) => {
                                    this.checkRecord(id);
                                });
                            });
                    });

                    return;
                }

                view.close();

                const count = o.count;

                this.collection
                    .fetch()
                    .then(() => {
                        if (count) {
                            let msg = 'massUpdateResult';

                            if (count === 1) {
                                msg = 'massUpdateResultSingle';
                            }

                            Espo.Ui.success(this.translate(msg, 'messages').replace('{count}', count));
                        }
                        else {
                            Espo.Ui.warning(this.translate('noRecordsUpdated', 'messages'));
                        }

                        if (allResultIsChecked) {
                            this.selectAllResult();

                            return;
                        }

                        ids.forEach(id => {
                            this.checkRecord(id);
                        });
                    });
            });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    massActionExport() {
        if (this.getConfig().get('exportDisabled') && !this.getUser().isAdmin()) {
            return;
        }

        this.export();
    }

    // noinspection JSUnusedGlobalSymbols
    massActionUnlink() {
        this.confirm({
            message: this.translate('unlinkSelectedRecordsConfirmation', 'messages'),
            confirmText: this.translate('Unlink'),
        }, () => {
            Espo.Ui.notify(' ... ');

            Espo.Ajax.deleteRequest(this.collection.url, {ids: this.checkedList})
                .then(() => {
                    Espo.Ui.success(this.translate('Unlinked'));

                    this.collection.fetch();

                    this.model.trigger('after:unrelate');
                });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    massActionConvertCurrency() {
        let ids = false;

        const allResultIsChecked = this.allResultIsChecked;

        if (!allResultIsChecked) {
            ids = this.checkedList;
        }

        this.createView('modalConvertCurrency', 'views/modals/mass-convert-currency', {
            entityType: this.entityType,
            ids: ids,
            where: this.collection.getWhere(),
            searchParams: this.collection.data,
            byWhere: this.allResultIsChecked,
            totalCount: this.collection.total,
        }, view => {
            view.render();

            this.listenToOnce(view, 'after:update', o => {
                if (o.idle) {
                    this.listenToOnce(view, 'close', () => {
                        this.collection
                            .fetch()
                            .then(() => {
                                if (allResultIsChecked) {
                                    this.selectAllResult();

                                    return;
                                }

                                ids.forEach((id) => {
                                    this.checkRecord(id);
                                });
                            });
                    });

                    return;
                }

                const count = o.count;

                this.collection
                    .fetch()
                    .then(() => {
                        if (count) {
                            let msg = 'massUpdateResult';

                            if (count === 1) {
                                msg = 'massUpdateResultSingle';
                            }

                            Espo.Ui.success(this.translate(msg, 'messages').replace('{count}', count));
                        }
                        else {
                            Espo.Ui.warning(this.translate('noRecordsUpdated', 'messages'));
                        }

                        if (allResultIsChecked) {
                            this.selectAllResult();

                            return;
                        }

                        ids.forEach(id => {
                            this.checkRecord(id);
                        });
                    });
            });
        });
    }

    /**
     * Add a mass action.
     *
     * @protected
     * @param {string} item An action.
     * @param {boolean} [allResult] To make available for all-result.
     * @param {boolean} [toBeginning] Add to the beginning of the list.
     */
    addMassAction(item, allResult, toBeginning) {
        toBeginning ?
            this.massActionList.unshift(item) :
            this.massActionList.push(item);

        if (allResult && this.collection.url === this.entityType) {
            toBeginning ?
                this.checkAllResultMassActionList.unshift(item) :
                this.checkAllResultMassActionList.push(item);
        }

        if (!this.checkboxesDisabled) {
            this.checkboxes = true;
        }
    }

    /**
     * Remove a mass action.
     *
     * @protected
     * @param {string} item An action.
     */
    removeMassAction(item) {
        let index = this.massActionList.indexOf(item);

        if (~index) {
            this.massActionList.splice(index, 1);
        }

        index = this.checkAllResultMassActionList.indexOf(item);

        if (~index) {
            this.checkAllResultMassActionList.splice(index, 1);
        }
    }

    /**
     * Remove an all-result mass action.
     *
     * @protected
     * @param {string} item An action.
     */
    removeAllResultMassAction(item) {
        const index = this.checkAllResultMassActionList.indexOf(item);

        if (~index) {
            this.checkAllResultMassActionList.splice(index, 1);
        }
    }

    /** @inheritDoc */
    setup() {
        if (typeof this.collection === 'undefined') {
            throw new Error('Collection has not been injected into views/record/list view.');
        }

        this.layoutLoadCallbackList = [];

        this.entityType = this.collection.entityType || null;
        this.scope = this.options.scope || this.entityType;

        this.massActionList = Espo.Utils.clone(this.massActionList);
        this.checkAllResultMassActionList = Espo.Utils.clone(this.checkAllResultMassActionList);
        this.buttonList = Espo.Utils.clone(this.buttonList);

        this.mandatorySelectAttributeList = Espo.Utils.clone(
            this.options.mandatorySelectAttributeList || this.mandatorySelectAttributeList || []
        );

        this.editDisabled = this.options.editDisabled || this.editDisabled ||
            this.getMetadata().get(['clientDefs', this.scope, 'editDisabled']);

        this.removeDisabled = this.options.removeDisabled || this.removeDisabled ||
            this.getMetadata().get(['clientDefs', this.scope, 'removeDisabled']);

        this.setupMassActions();

        if (this.selectable) {
            this.events['click .list a.link'] = (e) => {
                e.preventDefault();

                const id = $(e.target).attr('data-id');

                if (id) {
                    const model = this.collection.get(id);

                    if (this.checkboxes) {
                        const list = [];

                        list.push(model);

                        this.trigger('select', list);
                    }
                    else {
                        this.trigger('select', model);
                    }
                }

                e.stopPropagation();
            };
        }

        if ('showCount' in this.options) {
            this.showCount = this.options.showCount;
        }

        this.displayTotalCount = this.showCount && this.getConfig().get('displayListViewRecordCount');

        if ('displayTotalCount' in this.options) {
            this.displayTotalCount = this.options.displayTotalCount;
        }

        this.forceDisplayTopBar = this.options.forceDisplayTopBar || this.forceDisplayTopBar;

        if (!this.massActionList.length && !this.selectable) {
            this.checkboxes = false;
        }

        if (
            this.getUser().isPortal() &&
            !this.portalLayoutDisabled &&
            this.getMetadata().get(['clientDefs', this.scope, 'additionalLayouts', this.layoutName + 'Portal'])
        ) {
            this.layoutName += 'Portal';
        }

        this.wait(
            this.getHelper().processSetupHandlers(this, this.setupHandlerType)
        );

        this.listenTo(this.collection, 'sync', (c, r, options) => {
            this._renderEmpty = false;

            if (this.hasView('modal') && this.getView('modal').isRendered()) {
                return;
            }

            options = options || {};

            if (options.previousDataList) {
                const currentDataList = this.collection.models.map(model => {
                    return Espo.Utils.cloneDeep(model.attributes);
                });

                if (_.isEqual(currentDataList, options.previousDataList)) {
                    return;
                }
            }

            if (this.noRebuild) {
                this.noRebuild = null;

                return;
            }

            if (options.noRebuild) {
                this.noRebuild = null;

                return;
            }

            this.checkedList = [];
            this.allResultIsChecked = false;

            this.buildRows(() => {
                this.render();
            });
        });

        this.checkedList = [];

        if (!this.options.skipBuildRows) {
            this.buildRows();
        }

        this._renderEmpty = this.options.skipBuildRows;
    }

    afterRender() {
        this.$selectAllCheckbox = this.$el.find('input.select-all');

        if (this.allResultIsChecked) {
            this.selectAllResult();
        }
        else if (this.checkedList.length) {
            this.checkedList.forEach(id => {
                this.checkRecord(id);
            });
        }
    }

    /**
     * @private
     */
    setupMassActions() {
        if (this.massActionsDisabled) {
            this.massActionList = [];
            this.checkAllResultMassActionList = [];

            return;
        }

        if (!this.getAcl().checkScope(this.entityType, 'delete')) {
            this.removeMassAction('remove');
            this.removeMassAction('merge');
        }

        if (
            this.removeDisabled ||
            this.getMetadata().get(['clientDefs', this.scope, 'massRemoveDisabled'])
        ) {
            this.removeMassAction('remove');
        }

        if (!this.getAcl().checkScope(this.entityType, 'edit')) {
            this.removeMassAction('massUpdate');
            this.removeMassAction('merge');
        }

        if (
            this.getMetadata().get(['clientDefs', this.scope, 'mergeDisabled']) ||
            this.mergeDisabled
        ) {
            this.removeMassAction('merge');
        }

        this.massActionDefs = {
            ...this.getMetadata().get(['clientDefs', 'Global', 'massActionDefs']) || {},
            ...this.getMetadata().get(['clientDefs', this.scope, 'massActionDefs']) || {},
        };

        const metadataMassActionList = [
            ...this.getMetadata().get(['clientDefs', 'Global', 'massActionList']) || [],
            ...this.getMetadata().get(['clientDefs', this.scope, 'massActionList']) || [],
        ];

        const metadataCheckAllMassActionList = [
            ...this.getMetadata().get(['clientDefs', 'Global', 'checkAllResultMassActionList']) || [],
            ...this.getMetadata().get(['clientDefs', this.scope, 'checkAllResultMassActionList']) || [],
        ];

        metadataMassActionList.forEach(item => {
            const defs = /** @type {Espo.Utils~ActionAccessDefs & Espo.Utils~ActionAvailabilityDefs} */
                this.massActionDefs[item] || {};

            if (
                !Espo.Utils.checkActionAvailability(this.getHelper(), defs) ||
                !Espo.Utils.checkActionAccess(this.getAcl(), null, defs)
            ) {
                return;
            }

            this.massActionList.push(item);
        });

        this.checkAllResultMassActionList = this.checkAllResultMassActionList
            .filter(item => this.massActionList.includes(item));

        metadataCheckAllMassActionList.forEach(item => {
            if (this.collection.url !== this.entityType) {
                return;
            }

            if (~this.massActionList.indexOf(item)) {
                const defs = /** @type {Espo.Utils~ActionAccessDefs & Espo.Utils~ActionAvailabilityDefs} */
                    this.massActionDefs[item] || {};

                if (
                    !Espo.Utils.checkActionAvailability(this.getHelper(), defs) ||
                    !Espo.Utils.checkActionAccess(this.getAcl(), null, defs)
                ) {
                    return;
                }

                this.checkAllResultMassActionList.push(item);
            }
        });

        metadataMassActionList
            .concat(metadataCheckAllMassActionList)
            .forEach(action => {
                const defs = this.massActionDefs[action] || {};

                if (!defs.initFunction || !defs.handler) {
                    return;
                }

                const viewObject = this;

                this.wait(
                    new Promise((resolve) => {
                        Espo.loader.require(defs.handler, Handler => {
                            const handler = new Handler(viewObject);

                            handler[defs.initFunction].call(handler);

                            resolve();
                        });
                    })
                );
            });

        if (
            this.getConfig().get('exportDisabled') && !this.getUser().isAdmin() ||
            this.getAcl().getPermissionLevel('exportPermission') === 'no' ||
            this.getMetadata().get(['clientDefs', this.scope, 'exportDisabled']) ||
            this.exportDisabled
        ) {
            this.removeMassAction('export');
        }

        if (
            this.getAcl().getPermissionLevel('massUpdatePermission') !== 'yes' ||
            this.editDisabled ||
            this.massUpdateDisabled ||
            this.getMetadata().get(['clientDefs', this.scope, 'massUpdateDisabled'])
        ) {
            this.removeMassAction('massUpdate');
        }

        if (
            !this.massFollowDisabled &&
            this.getMetadata().get(['scopes', this.entityType, 'stream']) &&
            this.getAcl().check(this.entityType, 'stream') ||
            this.getMetadata().get(['clientDefs', this.scope, 'massFollowDisabled'])
        ) {
            this.addMassAction('follow');
            this.addMassAction('unfollow', true);
        }

        if (
            !this.massPrintPdfDisabled &&
            (this.getHelper().getAppParam('templateEntityTypeList') || []).includes(this.entityType)
        ) {
            this.addMassAction('printPdf');
        }

        if (this.options.unlinkMassAction && this.collection) {
            this.addMassAction('unlink', false, true);
        }

        if (
            !this.massConvertCurrencyDisabled &&
            !this.getMetadata().get(['clientDefs', this.scope, 'convertCurrencyDisabled']) &&
            this.getConfig().get('currencyList').length > 1 &&
            this.getAcl().checkScope(this.scope, 'edit') &&
            this.getAcl().getPermissionLevel('massUpdatePermission') === 'yes'
        ) {
            const currencyFieldList = this.getFieldManager().getEntityTypeFieldList(this.entityType, {
                type: 'currency',
                acl: 'edit',
            });

            if (currencyFieldList.length) {
                this.addMassAction('convertCurrency', true);
            }
        }

        this.setupMassActionItems();

        if (this.getUser().isAdmin()) {
            if (this.getMetadata().get(['formula', this.entityType, 'beforeSaveCustomScript'])) {
                this.addMassAction('recalculateFormula', true);
            }
        }

        if (this.collection.url !== this.entityType) {
            Espo.Utils.clone(this.checkAllResultMassActionList).forEach((item) => {
                this.removeAllResultMassAction(item);
            });
        }

        if (this.forcedCheckAllResultMassActionList) {
            this.checkAllResultMassActionList = Espo.Utils.clone(this.forcedCheckAllResultMassActionList);
        }

        if (this.getAcl().getPermissionLevel('massUpdatePermission') !== 'yes') {
            this.removeAllResultMassAction('remove');
        }

        Espo.Utils.clone(this.massActionList).forEach(item => {
            const propName = 'massAction' + Espo.Utils.upperCaseFirst(item) + 'Disabled';

            if (this[propName] || this.options[propName]) {
                this.removeMassAction(item);
            }
        });
    }

    /**
     * @protected
     */
    setupMassActionItems() {}

    /**
     * @protected
     */
    filterListLayout(listLayout) {
        if (this._cachedFilteredListLayout) {
            return this._cachedFilteredListLayout;
        }

        let forbiddenFieldList = this._cachedScopeForbiddenFieldList =
            this._cachedScopeForbiddenFieldList ||
            this.getAcl().getScopeForbiddenFieldList(this.entityType, 'read');

        if (this.layoutAclDisabled) {
            forbiddenFieldList = [];
        }

        if (!forbiddenFieldList.length) {
            this._cachedFilteredListLayout = listLayout;

            return this._cachedFilteredListLayout;
        }

        const filteredListLayout = Espo.Utils.clone(listLayout);

        for (const i in listLayout) {
            const name = listLayout[i].name;

            if (name && ~forbiddenFieldList.indexOf(name)) {
                filteredListLayout[i].customLabel = '';
                filteredListLayout[i].notSortable = true;
            }
        }

        this._cachedFilteredListLayout = filteredListLayout;

        return this._cachedFilteredListLayout;
    }

    /**
     * @protected
     * @param {function(Object[]):void} callback A callback.
     * @private
     */
    _loadListLayout(callback) {
        this.layoutLoadCallbackList.push(callback);

        if (this.layoutIsBeingLoaded) {
            return;
        }

        this.layoutIsBeingLoaded = true;

        const layoutName = this.layoutName;
        const layoutScope = this.layoutScope || this.collection.entityType;

        this.getHelper().layoutManager.get(layoutScope, layoutName, listLayout => {
            const filteredListLayout = this.filterListLayout(listLayout);

            this.layoutLoadCallbackList.forEach(callbackItem => {
                callbackItem(filteredListLayout);

                this.layoutLoadCallbackList = [];
                this.layoutIsBeingLoaded = false;
            });
        });
    }

    /**
     * Get a select-attribute list.
     *
     * @param {function(string[]):void} callback A callback.
     */
    getSelectAttributeList(callback) {
        if (this.scope === null) {
            callback(null);

            return;
        }

        if (this.listLayout) {
            const attributeList = this.fetchAttributeListFromLayout();

            callback(attributeList);

            return;
        }

        this._loadListLayout(listLayout => {
            this.listLayout = listLayout;

            let attributeList = this.fetchAttributeListFromLayout();

            if (this.mandatorySelectAttributeList) {
                attributeList = attributeList.concat(this.mandatorySelectAttributeList);
            }

            callback(attributeList);
        });
    }

    /**
     * @protected
     * @return {string[]}
     */
    fetchAttributeListFromLayout() {
        const selectProvider = new SelectProvider(
            this.getHelper().layoutManager,
            this.getHelper().metadata,
            this.getHelper().fieldManager
        );

        return selectProvider.getFromLayout(this.entityType, this.listLayout);
    }

    /**
     * @protected
     */
    _getHeaderDefs() {
        const defs = [];

        for (const i in this.listLayout) {
            let width = false;

            if ('width' in this.listLayout[i] && this.listLayout[i].width !== null) {
                width = this.listLayout[i].width + '%';
            }
            else if ('widthPx' in this.listLayout[i]) {
                width = this.listLayout[i].widthPx;
            }

            const itemName = this.listLayout[i].name;
            const label = this.listLayout[i].label || itemName;

            const item = {
                name: itemName,
                isSortable: !(this.listLayout[i].notSortable || false),
                width: width,
                align: ('align' in this.listLayout[i]) ? this.listLayout[i].align : false,
            };

            if ('customLabel' in this.listLayout[i]) {
                item.customLabel = this.listLayout[i].customLabel;
                item.hasCustomLabel = true;
                item.label = item.customLabel;
            }
            else {
                item.label = this.translate(label, 'fields', this.collection.entityType);
            }

            if (this.listLayout[i].noLabel) {
                item.label = null;
            }

            if (item.isSortable) {
                item.isSorted = this.collection.orderBy === itemName;

                if (item.isSorted) {
                    item.isDesc = this.collection.order === 'desc' ;
                }
            }

            defs.push(item);
        }

        const isCustomSorted =
            this.collection.orderBy !== this.collection.defaultOrderBy ||
            this.collection.order !== this.collection.defaultOrder;

        if (this.rowActionsView && !this.rowActionsDisabled || isCustomSorted) {
            let html = null;

            if (isCustomSorted) {
                html =
                    $('<a>')
                        .attr('role', 'button')
                        .attr('tabindex', '0')
                        .addClass('reset-custom-order')
                        .attr('title', this.translate('Reset'))
                        .append(
                            $('<span>').addClass('fas fa-times fa-sm')
                        )
                        .get(0).outerHTML
            }

            defs.push({
                width: this.rowActionsColumnWidth,
                html: html,
                className: 'action-cell',
            });
        }

        return defs;
    }

    /**
     * @protected
     */
    _convertLayout(listLayout, model) {
        model = model || this.collection.prepareModel();

        const layout = [];

        if (this.checkboxes) {
            layout.push({
                name: 'r-checkboxField',
                columnName: 'r-checkbox',
                template: 'record/list-checkbox',
            });
        }

        for (const i in listLayout) {
            const col = listLayout[i];
            const type = col.type || model.getFieldType(col.name) || 'base';

            if (!col.name) {
                continue;
            }

            const item = {
                columnName: col.name,
                name: col.name + 'Field',
                view: col.view ||
                    model.getFieldParam(col.name, 'view') ||
                    this.getFieldManager().getViewName(type),
                options: {
                    defs: {
                        name: col.name,
                        params: col.params || {}
                    },
                    mode: 'list',
                },
            };

            if (col.width) {
                item.options.defs.width = col.width;
            }

            if (col.widthPx) {
                item.options.defs.widthPx = col.widthPx;
            }

            if (col.link) {
                item.options.mode = 'listLink';
            }
            if (col.align) {
                item.options.defs.align = col.align;
            }

            if (col.options) {
                for (const optionName in col.options) {
                    if (typeof item.options[optionName] !== 'undefined') {
                        continue;
                    }

                    item.options[optionName] = col.options[optionName];
                }
            }

            layout.push(item);
        }

        if (this.rowActionsView && !this.rowActionsDisabled) {
            layout.push(this.getRowActionsDefs());
        }

        return layout;
    }

    /**
     * Select a record.
     *
     * @param {string} id An ID.
     * @param {JQuery} [$target]
     * @param {boolean} [isSilent] Do not trigger the `check` event.
     */
    checkRecord(id, $target, isSilent) {
        if (!this.collection.get(id)) {
            return;
        }

        $target = $target || this.$el.find('.record-checkbox[data-id="' + id + '"]');

        if ($target.length) {
            $target.get(0).checked = true;
            $target.closest('tr').addClass('active');
        }

        const index = this.checkedList.indexOf(id);

        if (index === -1) {
            this.checkedList.push(id);
        }

        this.handleAfterCheck(isSilent);
    }

    /**
     * Unselect a record.
     *
     * @param {string} id An ID.
     * @param {JQuery} [$target]
     * @param {boolean} [isSilent] Do not trigger the `check` event.
     */
    uncheckRecord(id, $target, isSilent) {
        $target = $target || this.$el.find('.record-checkbox[data-id="' + id + '"]');

        if ($target.get(0)) {
            $target.get(0).checked = false;
            $target.closest('tr').removeClass('active');
        }

        const index = this.checkedList.indexOf(id);

        if (index !== -1) {
            this.checkedList.splice(index, 1);
        }

        this.handleAfterCheck(isSilent);
    }

    /**
     * @protected
     * @param {boolean} [isSilent]
     */
    handleAfterCheck(isSilent) {
        if (this.checkedList.length) {
            this.showActions();
        }
        else {
            this.hideActions();
        }

        if (this.checkedList.length === this.collection.models.length) {
            this.$el.find('.select-all').prop('checked', true);
        }
        else {
            this.$el.find('.select-all').prop('checked', false);
        }

        if (!isSilent) {
            this.trigger('check');
        }
    }

    /**
     * Get row-actions defs.
     *
     * @return {Object}
     */
    getRowActionsDefs() {
        const options = {
            defs: {
                params: {}
            },
        };

        if (this.options.rowActionsOptions) {
            for (const item in this.options.rowActionsOptions) {
                options[item] = this.options.rowActionsOptions[item];
            }
        }

        return {
            columnName: 'buttons',
            name: 'buttonsField',
            view: this.rowActionsView,
            options: options
        };
    }

    /**
     * Get selected models.
     *
     * @return {module:model[]}
     */
    getSelected() {
        const list = [];

        this.$el.find('input.record-checkbox:checked').each((i, el) => {
            const id = $(el).attr('data-id');
            const model = this.collection.get(id);

            list.push(model);
        });

        return list;
    }

    /**
     * @protected
     */
    getInternalLayoutForModel(callback, model) {
        const scope = model.entityType;

        if (this._internalLayout === null) {
            this._internalLayout = {};
        }

        if (!(scope in this._internalLayout)) {
            this._internalLayout[scope] = this._convertLayout(this.listLayout[scope], model);
        }

        callback(this._internalLayout[scope]);
    }

    /**
     * @protected
     */
    getInternalLayout(callback, model) {
        if (
            (this.scope === null) &&
            !Array.isArray(this.listLayout)
        ) {
            if (!model) {
                callback(null);

                return;
            }

            this.getInternalLayoutForModel(callback, model);

            return;
        }

        if (this._internalLayout !== null) {
            callback(this._internalLayout);

            return;
        }

        if (this.listLayout !== null) {
            this._internalLayout = this._convertLayout(this.listLayout);

            callback(this._internalLayout);

            return;
        }

        this._loadListLayout(listLayout => {
            this.listLayout = listLayout;
            this._internalLayout = this._convertLayout(listLayout);

            callback(this._internalLayout);
        });
    }

    /**
     * Compose a cell selector for a layout item.
     *
     * @param {module:model} model A model.
     * @param {Object} item An item.
     * @return {string}
     */
    getItemEl(model, item) {
        return this.getSelector() +
            ' tr[data-id="' + model.id + '"]' +
            ' td.cell[data-name="' + item.columnName + '"]';
    }

    prepareInternalLayout(internalLayout, model) {
        internalLayout.forEach((item) => {
            item.el = this.getItemEl(model, item);
        });
    }

    /**
     * Build a row.
     *
     * @param {number} i An index.
     * @param {module:model} model A model.
     * @param {function(module:view):void} [callback] A callback.
     */
    buildRow(i, model, callback) {
        const key = model.id;

        this.rowList.push(key);

        this.getInternalLayout(internalLayout => {
            internalLayout = Espo.Utils.cloneDeep(internalLayout);

            this.prepareInternalLayout(internalLayout, model);

            const acl = {
                edit: this.getAcl().checkModel(model, 'edit') && !this.editDisabled,
                delete: this.getAcl().checkModel(model, 'delete') && !this.removeDisabled,
            };

            this.createView(key, 'views/base', {
                model: model,
                acl: acl,
                selector: '.list-row[data-id="' + key + '"]',
                optionsToPass: ['acl'],
                layoutDefs: {
                    type: this._internalLayoutType,
                    layout: internalLayout
                },
                setViewBeforeCallback: this.options.skipBuildRows && !this.isRendered(),
            }, callback);
        }, model);
    }

    /**
     * Build rows.
     *
     * @param {function():void} [callback] A callback.
     */
    buildRows(callback) {
        this.checkedList = [];

        this.rowList = [];

        if (this.collection.length <= 0) {
            if (typeof callback === 'function') {
                callback();

                this.trigger('after:build-rows');
            }

            return;
        }

        let iteration = 0;
        const repeatCount = !this.pagination ? 1 : 2;

        const callbackWrapped = () => {
            iteration++;

            if (iteration === repeatCount) {
                if (typeof callback === 'function') {
                    callback();
                }
            }
        };

        this.wait(true);

        const modelList = this.collection.models;
        const count = modelList.length;
        let builtCount = 0;

        modelList.forEach(model => {
            this.buildRow(iteration, model, () => {
                builtCount++;

                if (builtCount === count) {
                    callbackWrapped();

                    this.wait(false);

                    this.trigger('after:build-rows');
                }
            });
        });

        if (this.pagination) {
            this.createView('pagination', 'views/record/list-pagination', {
                collection: this.collection,
            }, callbackWrapped);
        }
    }

    /**
     * Show more records.
     *
     * @param {{skipNotify?: boolean}} [options]
     * @param {module:collection} [collection]
     * @param {JQuery} [$list]
     * @param {JQuery} [$showMore]
     * @param {function(): void} [callback] A callback.
     */
    showMoreRecords(options, collection, $list, $showMore, callback) {
        collection = collection || this.collection;
        $showMore =  $showMore || this.$el.find('.show-more');
        $list = $list || this.$el.find(this.listContainerEl);
        options = options || {};

        const $container = this.$el.find('.list');

        $showMore.children('a').addClass('disabled');

        if (!options.skipNotify) {
            Espo.Ui.notify(' ... ');
        }

        const lengthBefore = collection.length;

        const final = () => {
            $showMore.parent().append($showMore);

            if (
                collection.total > collection.length + collection.lengthCorrection ||
                collection.total === -1
            ) {
                const moreCount = collection.total - collection.length - collection.lengthCorrection;
                const moreCountString = this.getNumberUtil().formatInt(moreCount);

                this.$el.find('.more-count').text(moreCountString);

                $showMore.removeClass('hidden');
                $container.addClass('has-show-more');
            }
            else {
                $showMore.remove();
                $container.removeClass('has-show-more');
            }

            $showMore.children('a').removeClass('disabled');

            if (this.allResultIsChecked) {
                this.$el
                    .find('input.record-checkbox')
                    .attr('disabled', 'disabled')
                    .prop('checked', true);
            }

            if (!options.skipNotify) {
                Espo.Ui.notify(false);
            }

            if (callback) {
                callback.call(this);
            }

            this.trigger('after:show-more', lengthBefore);
        };

        const initialCount = collection.length;

        const success = () => {
            if (!options.skipNotify) {
                Espo.Ui.notify(false);
            }

            $showMore.addClass('hidden');
            $container.removeClass('has-show-more');

            const rowCount = collection.length - initialCount;
            let rowsReady = 0;

            if (collection.length <= initialCount) {
                final();
            }

            for (let i = initialCount; i < collection.length; i++) {
                const model = collection.at(i);

                this.buildRow(i, model, view => {
                    const model = view.model;

                    const $existingRow = this.getDomRowItem(model.id);

                    if ($existingRow && $existingRow.length) {
                        $existingRow.remove();
                    }

                    $list.append(
                        $(this.getRowContainerHtml(model.id))
                    );

                    view.render()
                        .then(() => {
                            rowsReady++;

                            if (rowsReady === rowCount) {
                                final();
                            }
                        });
                });
            }

            this.noRebuild = true;
        };

        this.listenToOnce(collection, 'update', (collection, o) => {
            if (o.changes.merged.length) {
                collection.lengthCorrection += o.changes.merged.length;
            }
        });

        // If using promise callback, then need to pass `noRebuild: true`.
        collection.fetch({
            success: success,
            remove: false,
            more: true,
        });
    }

    getDomRowItem(id) {
        return null;
    }

    /**
     * Compose a row-container HTML.
     *
     * @param {string} id A record ID.
     * @return {string} HTML.
     */
    getRowContainerHtml(id) {
        return $('<tr>')
            .attr('data-id', id)
            .addClass('list-row')
            .get(0).outerHTML;
    }

    actionQuickView(data) {
        data = data || {};

        const id = data.id;

        if (!id) {
            console.error("No id.");

            return;
        }

        let model = null;

        if (this.collection) {
            model = this.collection.get(id);
        }

        let scope = data.scope;

        if (!scope && model) {
            scope = model.entityType;
        }

        if (!scope) {
            scope = this.scope;
        }

        if (!scope) {
            console.error("No scope.");

            return;
        }

        if (this.quickDetailDisabled) {
            this.getRouter().navigate('#' + scope + '/view/' + id, {trigger: true});

            return;
        }

        const helper = new RecordModal(this.getMetadata(), this.getAcl());

        helper
            .showDetail(this, {
                id: id,
                scope: scope,
                model: model,
                rootUrl: this.options.keepCurrentRootUrl ?
                    this.getRouter().getCurrentUrl() : null,
                editDisabled: this.quickEditDisabled,
            })
            .then(view => {
                if (!model) {
                    return;
                }

                this.listenTo(view, 'after:save', model => {
                    this.trigger('after:save', model);
                });
            });
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * @param {Object.<string, *>} data
     */
    actionQuickEdit(data) {
        data = data || {};

        const id = data.id;

        if (!id) {
            console.error("No id.");

            return;
        }

        let model = null;

        if (this.collection) {
            model = this.collection.get(id);
        }

        let scope = data.scope;

        if (!scope && model) {
            scope = model.entityType;
        }

        if (!scope) {
            scope = this.scope;
        }

        if (!scope) {
            console.error("No scope.");

            return;
        }

        const viewName = this.getMetadata().get(['clientDefs', scope, 'modalViews', 'edit']) ||
            'views/modals/edit';

        if (!this.quickEditDisabled) {
            Espo.Ui.notify(' ... ');

            const options = {
                scope: scope,
                id: id,
                model: model,
                fullFormDisabled: data.noFullForm,
                returnUrl: this.getRouter().getCurrentUrl(),
                returnDispatchParams: {
                    controller: scope,
                    action: null,
                    options: {
                        isReturn: true,
                    },
                },
            };

            if (this.options.keepCurrentRootUrl) {
                options.rootUrl = this.getRouter().getCurrentUrl();
            }

            this.createView('modal', viewName, options, (view) => {
                view.once('after:render', () => {
                    Espo.Ui.notify(false);
                });

                view.render();

                this.listenToOnce(view, 'remove', () => {
                    this.clearView('modal');
                });

                this.listenToOnce(view, 'after:save', (m) => {
                    const model = this.collection.get(m.id);

                    if (model) {
                        model.set(m.getClonedAttributes());
                    }

                    this.trigger('after:save', m);
                });
            });

            return;
        }

        const options = {
            id: id,
            model: this.collection.get(id),
            returnUrl: this.getRouter().getCurrentUrl(),
            returnDispatchParams: {
                controller: scope,
                action: null,
                options: {
                    isReturn: true,
                }
            },
        };

        if (this.options.keepCurrentRootUrl) {
            options.rootUrl = this.getRouter().getCurrentUrl();
        }

        this.getRouter().navigate('#' + scope + '/edit/' + id, {trigger: false});
        this.getRouter().dispatch(scope, 'edit', options);
    }

    /**
     * Compose a row selector.
     *
     * @param {string} id A record ID.
     * @return {string}
     */
    getRowSelector(id) {
        return 'tr[data-id="' + id + '"]';
    }

    // noinspection JSUnusedGlobalSymbols
    actionQuickRemove(data) {
        data = data || {};

        const id = data.id;

        if (!id) {
            return;
        }

        const model = this.collection.get(id);

        if (!this.getAcl().checkModel(model, 'delete')) {
            Espo.Ui.error(this.translate('Access denied'));

            return;
        }

        this.confirm({
            message: this.translate('removeRecordConfirmation', 'messages', this.scope),
            confirmText: this.translate('Remove'),
        }, () => {
            this.collection.trigger('model-removing', id);
            this.collection.remove(model);

            Espo.Ui.notify(' ... ');

            model
                .destroy({wait: true, fromList: true})
                .then(() => {
                    Espo.Ui.success(this.translate('Removed'));

                    this.removeRecordFromList(id);
                })
                .catch(() => {
                    this.collection.push(model);
                });
        });
    }

    /**
     * @protected
     * @param {string} id An ID.
     */
    removeRecordFromList(id) {
        this.collection.remove(id);

        if (this.collection.total > 0) {
            this.collection.total--;
        }

        this.$el.find('.total-count-span').text(this.collection.total.toString());

        let index = this.checkedList.indexOf(id);

        if (index !== -1) {
            this.checkedList.splice(index, 1);
        }

        const key = id;

        this.clearView(key);

        index = this.rowList.indexOf(key);

        if (~index) {
            this.rowList.splice(index, 1);
        }

        this.removeRowHtml(id);
    }

    /**
     * @protected
     * @param {string} id An ID.
     */
    removeRowHtml(id) {
        this.$el.find(this.getRowSelector(id)).remove();

        if (
            this.collection.length === 0 &&
            (this.collection.total === 0 || this.collection.total === -2)
        ) {
            this.reRender();
        }
    }

    /**
     * @public
     * @param {string} id An ID.
     * @return {boolean}
     */
    isIdChecked(id) {
        return this.checkedList.indexOf(id) !== -1;
    }

    // noinspection JSUnusedGlobalSymbols
    getTableMinWidth() {
        if (!this.listLayout) {
            return;
        }

        let totalWidth = 0;
        let totalWidthPx = 0;
        let emptyCount = 0;
        let columnCount = 0;

        this.listLayout.forEach((item) => {
            columnCount ++;

            if (item.widthPx) {
                totalWidthPx += item.widthPx;

                return;
            }

            if (item.width) {
                totalWidth += item.width;

                return;
            }

            emptyCount ++;
        });

        if (this.rowActionsView && !this.rowActionsDisabled) {
            totalWidthPx += this.rowActionsColumnWidth;
        }

        if (this.checkboxes) {
            totalWidthPx += this.checkboxColumnWidth;
        }

        let minWidth;

        if (totalWidth >= 100) {
            minWidth = columnCount * this.minColumnWidth;
        }
        else {
            minWidth = (totalWidthPx + this.minColumnWidth * emptyCount) / (1 - totalWidth / 100);
            minWidth = Math.round(minWidth);
        }

        return minWidth;
    }
}

export default ListRecordView;
PK]�I���$�$views/record/detail-side.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/record/detail-side */

import PanelsContainerRecordView from 'views/record/panels-container';

/**
 * A detail-side record view.
 */
class DetailSideRecordView extends PanelsContainerRecordView {

    /** @inheritDoc */
    template = 'record/side'

    /** @inheritDoc */
    mode = 'detail'
    readOnly = false
    inlineEditDisabled = false
    name = 'side'
    defaultPanel = true

    /**
     * A panel list.
     *
     * @protected
     * @type {module:views/record/panels-container~panel[]}
     */
    panelList = []

    /**
     * A default panel.
     *
     * @type {module:views/record/panels-container~panel}
     */
    defaultPanelDefs = {
        name: 'default',
        label: false,
        view: 'views/record/panels/default-side',
        isForm: true,
        options: {
            fieldList: [
                {
                    name: ':assignedUser'
                },
                {
                    name: 'teams'
                },
            ],
        }
    }

    init() {
        this.panelList = this.options.panelList || this.panelList;
        this.scope = this.entityType = this.options.model.entityType;

        this.recordHelper = this.options.recordHelper;

        this.panelList = Espo.Utils.clone(this.panelList);

        this.readOnlyLocked = this.options.readOnlyLocked || this.readOnly;
        this.readOnly = this.options.readOnly || this.readOnly;
        this.inlineEditDisabled = this.options.inlineEditDisabled || this.inlineEditDisabled;

        this.recordViewObject = this.options.recordViewObject;
    }

    /** @inheritDoc */
    setupPanels() {}

    setup() {
        this.type = this.mode;

        if ('type' in this.options) {
            this.type = this.options.type;
        }

        this.setupPanels();

        if (!this.additionalPanelsDisabled) {
            let additionalPanels = this.getMetadata()
                .get(['clientDefs', this.scope, 'sidePanels', this.type]) || [];

            additionalPanels.forEach((panel) => {
                this.panelList.push(panel);
            });
        }

        this.panelList = this.panelList.filter((p) => {
            if (p.aclScope) {
                if (!this.getAcl().checkScope(p.aclScope)) {
                    return;
                }
            }

            if (p.accessDataList) {
                if (!Espo.Utils.checkAccessDataList(p.accessDataList, this.getAcl(), this.getUser())) {
                    return false;
                }
            }

            return true;
        });

        this.panelList = this.panelList.map((p) => {
            let item = Espo.Utils.clone(p);

            if (this.recordHelper.getPanelStateParam(p.name, 'hidden') !== null) {
                item.hidden = this.recordHelper.getPanelStateParam(p.name, 'hidden');
            } else {
                this.recordHelper.setPanelStateParam(p.name, 'hidden', item.hidden || false);
            }

            return item;
        });

        this.panelList.forEach((item) => {
            item.actionsViewKey = item.name + 'Actions';
        });

        this.wait(
            Promise.all([
                new Promise((resolve) => {
                    this.getHelper().layoutManager.get(
                        this.scope,
                        'sidePanels' + Espo.Utils.upperCaseFirst(this.type),
                        (layoutData) => {
                            this.layoutData = layoutData;

                            resolve();
                        });
                }),
                new Promise((resolve) => {
                    if (
                        !this.defaultPanel ||
                        this.getMetadata().get(['clientDefs', this.scope, 'defaultSidePanelDisabled']) ||
                        this.getMetadata().get(['clientDefs', this.scope, 'defaultSidePanel', this.type]) ||
                        this.getMetadata().get(['clientDefs', this.scope, 'defaultSidePanelFieldLists', this.type]) ||
                        this.getMetadata().get(['clientDefs', this.scope, 'defaultSidePanelFieldList'])
                    ) {
                        resolve();

                        return;
                    }

                    this.getHelper()
                        .layoutManager
                        .get(this.scope, 'defaultSidePanel', (layoutData) => {
                            this.defaultSidePanelLayoutData = layoutData;

                            resolve();
                        });
                }),
            ]).then(() => {
                if (this.defaultPanel) {
                    this.setupDefaultPanel();
                }

                this.alterPanels();
                this.setupPanelsFinal();
                this.setupPanelViews();
            })
        );
    }

    /**
     * Set up a default panel.
     *
     * @protected
     */
    setupDefaultPanel() {
        let met = false;

        this.panelList.forEach((item) => {
            if (item.name === 'default') {
                met = true;
            }
        });

        if (met) {
            return;
        }

        let defaultPanelDefs = this.getMetadata().get(['clientDefs', this.scope, 'defaultSidePanel', this.type]);

        if (defaultPanelDefs === false) {
            return;
        }

        if (this.getMetadata().get(['clientDefs', this.scope, 'defaultSidePanelDisabled'])) {
            return;
        }

        defaultPanelDefs = defaultPanelDefs || this.defaultPanelDefs;

        if (!defaultPanelDefs) {
            return;
        }

        defaultPanelDefs = Espo.Utils.cloneDeep(defaultPanelDefs);

        defaultPanelDefs.view = this.getMetadata().get(['clientDefs', this.scope, 'defaultSidePanelView']) ||
            defaultPanelDefs.view;

        let fieldList = this.getMetadata()
            .get(['clientDefs', this.scope, 'defaultSidePanelFieldLists', this.type]);

        if (!fieldList) {
            fieldList = this.getMetadata().get(['clientDefs', this.scope, 'defaultSidePanelFieldList']);
        }

        if (!fieldList && this.defaultSidePanelLayoutData) {
            fieldList = this.defaultSidePanelLayoutData;
        }

        if (fieldList) {
            defaultPanelDefs.options = defaultPanelDefs.options || {};
            defaultPanelDefs.options.fieldList = fieldList;
        }

        fieldList = defaultPanelDefs.options.fieldList;

        if (fieldList && fieldList.length) {
            fieldList.forEach((item, i) => {
                if (typeof item !== 'object') {
                    item = {name: item};

                    fieldList[i] = item;
                }

                if (item.name === ':assignedUser') {
                    if (this.model.hasField('assignedUsers')) {
                        item.name = 'assignedUsers';

                        if (!this.model.getFieldParam('assignedUsers', 'view')) {
                            item.view = 'views/fields/assigned-users';
                        }
                    }
                    else if (this.model.hasField('assignedUser')) {
                        item.name = 'assignedUser';
                    }
                    else {
                        defaultPanelDefs.options.fieldList[i] = {};
                    }
                }
            });

            const fieldDefs = this.getMetadata().get(['entityDefs', this.entityType, 'fields']) || {};

            defaultPanelDefs.options.fieldList = fieldList.filter(item => {
                const defs = fieldDefs[item.name] || {}

                return !defs.disabled;
            });
        }

        this.panelList.unshift(defaultPanelDefs);
    }
}

export default DetailSideRecordView;
PK]k��ggviews/record/list-expanded.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module module:views/record/list-expanded */

import ListRecordView from 'views/record/list';

class ListExpandedRecordView extends ListRecordView {

    template = 'record/list-expanded'

    checkboxes = false
    selectable = false
    rowActionsView = false
    _internalLayoutType = 'list-row-expanded'
    presentationType = 'expanded'
    pagination = false
    header = false
    _internalLayout = null
    checkedList = null
    listContainerEl = '.list > ul'

    setup() {
        super.setup();

        this.on('after:save', model => {
            let view = this.getView(model.id);

            if (!view) {
                return;
            }

            view.reRender();
        });

        // Prevents displaying an empty buttons container.
        this.displayTotalCount = false;
    }

    _loadListLayout(callback) {
        let type = this.type + 'Expanded';

        this.layoutLoadCallbackList.push(callback);

        if (this.layoutIsBeingLoaded) {
            return;
        }

        this.layoutIsBeingLoaded = true;

        this._helper.layoutManager.get(this.collection.entityType, type, listLayout => {
            this.layoutLoadCallbackList.forEach(c => {
                c(listLayout);

                this.layoutLoadCallbackList = [];
                this.layoutIsBeingLoaded = false;
            });
        });
    }

    _convertLayout(listLayout, model) {
        model = model || this.collection.prepareModel();

        let layout = {
            rows: [],
            right: false,
        };

        for (let i in listLayout.rows) {
            let row = listLayout.rows[i];
            let layoutRow = [];

            for (let j in row) {
                let rowItem = row[j];
                let type = rowItem.type || model.getFieldType(rowItem.name) || 'base';

                let item = {
                    name: rowItem.name + 'Field',
                    field: rowItem.name,
                    view: rowItem.view ||
                        model.getFieldParam(rowItem.name, 'view') ||
                        this.getFieldManager().getViewName(type),
                    options: {
                        defs: {
                            name: rowItem.name,
                            params: rowItem.params || {}
                        },
                        mode: 'list',
                    },
                };

                if (rowItem.options) {
                    for (let optionName in rowItem.options) {
                        if (typeof item.options[optionName] !== 'undefined') {
                            continue;
                        }

                        item.options[optionName] = rowItem.options[optionName];
                    }
                }

                if (rowItem.link) {
                    item.options.mode = 'listLink';
                }

                layoutRow.push(item);
            }

            layout.rows.push(layoutRow);
        }

        if ('right' in listLayout) {
            if (listLayout.right) {
                let name = listLayout.right.name || 'right';

                layout.right = {
                    field: name,
                    name: name,
                    view: listLayout.right.view,
                    options: {
                        defs: {
                            params: {
                                width: listLayout.right.width || '7%',
                            }
                        }
                    },
                };
            }
        }
        else {
            if (this.rowActionsView) {
                layout.right = this.getRowActionsDefs();
            }
        }

        return layout;
    }

    getRowSelector(id) {
        return 'li[data-id="' + id + '"]';
    }

    getItemEl(model, item) {
        let name = item.field || item.columnName;

        return this.getSelector() + ' li[data-id="' + model.id + '"] .cell[data-name="' + name+ '"]';
    }

    getRowContainerHtml(id) {
        return $('<li>')
            .attr('data-id', id)
            .addClass('list-group-item list-row')
            .get(0).outerHTML;
    }

    prepareInternalLayout(internalLayout, model) {
        let rows = internalLayout.rows || [];

        rows.forEach((row) => {
            row.forEach((col) => {
                col.el = this.getItemEl(model, col);
            });
        });

        if (internalLayout.right) {
            internalLayout.right.el = this.getItemEl(model, internalLayout.right);
        }
    }

    fetchAttributeListFromLayout() {
        var list = [];

        if (this.listLayout.rows) {
            this.listLayout.rows.forEach((row) => {
                row.forEach(item => {
                    if (!item.name) {
                        return;
                    }

                    var field = item.name;

                    var fieldType = this.getMetadata().get(['entityDefs', this.scope, 'fields', field, 'type']);

                    if (!fieldType) {
                        return;
                    }

                    this.getFieldManager()
                        .getEntityTypeFieldAttributeList(this.scope, field)
                        .forEach((attribute) => {
                            list.push(attribute);
                        });
                });
            });
        }

        return list;
    }
}

export default ListExpandedRecordView;
PK]z#�1�#�#views/record/merge.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';
import $ from 'jquery';

class MergeRecordView extends View {

    template = 'record/merge'

    scope = ''

    events = {
        /** @this MergeRecordView */
        'change input[type="radio"][name="check-all"]': function (e) {
            e.stopPropagation();

            let id = e.currentTarget.value;

            $('input[data-id="' + id + '"]').prop('checked', true);
        },
        /** @this MergeRecordView */
        'click button[data-action="cancel"]': function () {
            this.getRouter().navigate('#' + this.scope, {trigger: true});
        },
        /** @this MergeRecordView */
        'click button[data-action="merge"]': function () {
            let id = $('input[type="radio"][name="check-all"]:checked').val();

            let model;

            this.models.forEach(m => {
                if (m.id === id) {
                    model = m;
                }
            });

            let attributes = {};

            $('input.field-radio:checked').each((i, el) => {
                let field = el.name;
                let id = $(el).attr('data-id');

                if (model.id === id) {
                    return;
                }

                let fieldType = model.getFieldParam(field, 'type');
                let fields = this.getFieldManager().getActualAttributeList(fieldType, field);

                let modelFrom;

                this.models.forEach(itemModel => {
                    if (itemModel.id === id) {
                        modelFrom = itemModel;
                    }
                });

                fields.forEach(field => {
                    attributes[field] = modelFrom.get(field);
                });
            });

            Espo.Ui.notify(' ... ');

            let sourceIdList = this.models
                .filter(m => m.id !== model.id)
                .map(m => m.id);

            Espo.Ajax
                .postRequest('Action', {
                    entityType: this.scope,
                    action: 'merge',
                    id: model.id,
                    data: {
                        sourceIdList: sourceIdList,
                        attributes: attributes,
                    },
                })
                .then(() => {
                    Espo.Ui.success(this.translate('Merged'), {suppress: true});

                    this.getRouter().navigate('#' + this.scope + '/view/' + model.id, {trigger: true});

                    if (this.collection) {
                        this.collection.fetch();
                    }
                });
        }
    }

    data() {
        let rows = [];

        this.fields.forEach(field => {
            let o = {
                name: field,
                scope: this.scope,
            };

            o.columns = [];

            this.models.forEach(model => {
                o.columns.push({
                    id: model.id,
                    fieldVariable: model.id + '-' + field,
                    isReadOnly: this.readOnlyFields[field] || false,
                });
            });

            rows.push(o);
        });

        return {
            rows: rows,
            modelList: this.models,
            scope: this.scope,
            hasCreatedAt: this.hasCreatedAt,
            width: Math.round(((80 - this.models.length * 5) / this.models.length * 10)) / 10,
            dataList: this.getDataList(),
        };
    }

    afterRender() {
        $('input[data-id="' + this.models[0].id + '"]').prop('checked', true);
    }

    setup() {
        this.scope = this.options.models[0].name;
        this.models = this.options.models;

        let fieldManager = this.getFieldManager();

        let differentFieldList = [];
        let fieldsDefs = this.models[0].defs.fields;

        this.readOnlyFields = {};

        for (let field in fieldsDefs) {
            let type = fieldsDefs[field].type;

            if (type === 'linkMultiple') {
                continue;
            }

            if (
                fieldsDefs[field].disabled ||
                fieldsDefs[field].utility ||
                fieldsDefs[field].mergeDisabled
            ) {
                continue;
            }

            if (
                field === 'createdAt' ||
                field === 'modifiedAt'
            ) {
                continue;
            }

            if (fieldManager.isMergeable(type)) {
                let actualAttributeList = fieldManager.getActualAttributeList(type, field);

                let differs = false;

                actualAttributeList.forEach(field => {
                    let values = [];

                    this.models.forEach(model => {
                        values.push(model.get(field));
                    });

                    let firstValue = values[0];

                    values.forEach(value => {
                        if (!_.isEqual(firstValue, value)) {
                            differs = true;
                        }
                    });
                });

                if (differs) {
                    differentFieldList.push(field);

                    if (this.models[0].isFieldReadOnly(field)) {
                        this.readOnlyFields[field] = true;
                    }
                }
            }
        }

        differentFieldList.sort((v1, v2) => {
            return this.translate(v1, 'fields', this.scope)
                .localeCompare(this.translate(v2, 'fields', this.scope));
        });

        differentFieldList = differentFieldList.sort((v1, v2) => {
            if (!this.readOnlyFields[v1] && this.readOnlyFields[v2]) {
                return -1;
            }

            return 1;
        });

        this.fields = differentFieldList;

        this.fields.forEach(field => {
            let type = this.models[0].getFieldParam(field, 'type');

            this.models.forEach((model) => {
                let viewName = model.getFieldParam(field, 'view') ||
                    this.getFieldManager().getViewName(type);

                this.createView(model.id + '-' + field, viewName, {
                    model: model,
                    fullSelector: '.merge [data-id="' + model.id + '"] .field[data-name="' + field + '"]',
                    defs: {
                        name: field,
                    },
                    mode: 'detail',
                    readOnly: true,
                });
            });
        });

        this.hasCreatedAt = this.getMetadata().get(['entityDefs', this.scope, 'fields', 'createdAt']);

        if (this.hasCreatedAt) {
            this.models.forEach(model => {
                this.createView(model.id + '-' + 'createdAt', 'views/fields/datetime', {
                    model: model,
                    fullSelector: '.merge [data-id="' + model.id + '"] .field[data-name="createdAt"]',
                    defs: {
                        name: 'createdAt',
                    },
                    mode: 'detail',
                    readOnly: true,
                });
            });
        }
    }

    getDataList() {
        let dataList = [];

        this.models.forEach(model => {
            var o = {};

            o.id = model.id;
            o.name = model.get('name');
            o.createdAtViewName = model.id + '-' + 'createdAt';

            dataList.push(o);
        });

        return dataList;
    }
}

export default MergeRecordView;
PK]�������views/record/search.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/record/search */

import View from 'view';
import StoredTextSearch from 'helpers/misc/stored-text-search';

/**
 * A search panel view.
 */
class SearchView extends View {

    template = 'record/search'

    scope = ''
    entityType = ''
    /** @type {module:search-manager} */
    searchManager = null
    fieldFilterList = null
    /** @type {Object.<string, string>|null}*/
    fieldFilterTranslations = null

    textFilter = ''
    primary = null
    presetFilterList = null
    advanced = null
    bool = null

    disableSavePreset = false
    textFilterDisabled = false
    toShowApplyFiltersButton = false
    toShowResetFiltersText = false
    isSearchedWithAdvancedFilter = false

    viewModeIconClassMap = {
        list: 'fas fa-align-justify',
        kanban: 'fas fa-align-left fa-rotate-90',
    }

    FIELD_QUICK_SEARCH_COUNT_THRESHOLD = 4

    autocompleteLimit = 7

    data() {
        return {
            scope: this.scope,
            entityType: this.entityType,
            textFilter: this.textFilter,
            bool: this.bool || {},
            boolFilterList: this.boolFilterList,
            hasFieldQuickSearch: this.fieldFilterList.length >= this.FIELD_QUICK_SEARCH_COUNT_THRESHOLD,
            filterFieldDataList: this.getFilterFieldDataList(),
            filterDataList: this.getFilterDataList(),
            presetName: this.presetName,
            presetFilterList: this.getPresetFilterList(),
            leftDropdown: this.isLeftDropdown(),
            textFilterDisabled: this.textFilterDisabled,
            viewMode: this.viewMode,
            viewModeDataList: this.viewModeDataList || [],
            hasViewModeSwitcher: this.viewModeList && this.viewModeList.length > 1,
            isWide: this.options.isWide,
            toShowApplyFiltersButton: this.toShowApplyFiltersButton,
            toShowResetFiltersText: this.toShowResetFiltersText,
        };
    }

    setup() {
        this.entityType = this.collection.entityType;
        this.scope = this.options.scope || this.entityType;

        /** @type {module:search-manager} */
        this.searchManager = this.options.searchManager;

        /** @private */
        this.storedTextSearchHelper = new StoredTextSearch(this.scope, this.getHelper().storage);

        this.textSearchStoringDisabled = this.getPreferences().get('textSearchStoringDisabled');

        this.textFilterDisabled = this.options.textFilterDisabled || this.textFilterDisabled ||
            this.getMetadata().get(['clientDefs', this.scope, 'textFilterDisabled']);

        if ('disableSavePreset' in this.options) {
            this.disableSavePreset = this.options.disableSavePreset;
        }

        this.viewMode = this.options.viewMode;
        this.viewModeList = this.options.viewModeList;

        this.addReadyCondition(() => {
            return this.fieldFilterList !== null;
        });

        this.boolFilterList = Espo.Utils
            .clone(this.getMetadata().get(['clientDefs', this.scope, 'boolFilterList']) || [])
            .filter(item => {
                if (typeof item === 'string') {
                    return true;
                }

                item = item || {};

                if (item.aux) {
                    return false;
                }

                if (item.inPortalDisabled && this.getUser().isPortal()) {
                    return false;
                }

                if (item.isPortalOnly && !this.getUser().isPortal()) {
                    return false;
                }

                if (item.accessDataList) {
                    if (!Espo.Utils.checkAccessDataList(item.accessDataList, this.getAcl(), this.getUser())) {
                        return false;
                    }
                }

                return true;
            })
            .map(item => {
                if (typeof item === 'string') {
                    return item;
                }

                item = item || {};

                return item.name;
            });

        this.fieldFilterTranslations = {};

        let forbiddenFieldList = this.getAcl().getScopeForbiddenFieldList(this.entityType) || [];

        this.wait(
            new Promise(resolve => {
                this.getHelper().layoutManager.get(this.entityType, 'filters', list => {
                    this.fieldFilterList = [];

                    (list || []).forEach(field => {
                        if (~forbiddenFieldList.indexOf(field)) {
                            return;
                        }

                        this.fieldFilterList.push(field);
                        this.fieldFilterTranslations[field] = this.translate(field, 'fields', this.entityType);
                    });

                    resolve();
                });
            })
        );

        let filterList = this.options.filterList ||
            this.getMetadata().get(['clientDefs', this.scope, 'filterList']) || [];

        this.presetFilterList = Espo.Utils.clone(filterList).filter((item) => {
            if (typeof item === 'string') {
                return true;
            }

            item = item || {};

            if (item.aux) {
                return false;
            }

            if (item.inPortalDisabled && this.getUser().isPortal()) {
                return false;
            }

            if (item.isPortalOnly && !this.getUser().isPortal()) {
                return false;
            }

            if (item.accessDataList) {
                if (!Espo.Utils.checkAccessDataList(item.accessDataList, this.getAcl(), this.getUser())) {
                    return false;
                }
            }

            return true;
        });

        ((this.getPreferences().get('presetFilters') || {})[this.scope] || [])
            .forEach(item => {
                this.presetFilterList.push(item);
            });

        if (this.getMetadata().get(['scopes', this.entityType, 'stream'])) {
            this.boolFilterList.push('followed');
        }

        this.loadSearchData();

        if (this.hasAdvancedFilter()) {
            this.isSearchedWithAdvancedFilter = true;
        }

        if (this.presetName) {
            let hasPresetListed = false;

            for (let i in this.presetFilterList) {
                let item = this.presetFilterList[i] || {};

                let name = (typeof item === 'string') ? item : item.name;

                if (name === this.presetName) {
                    hasPresetListed = true;

                    break;
                }
            }

            if (!hasPresetListed) {
                this.presetFilterList.push(this.presetName);
            }
        }

        this.model = this.collection.prepareModel();

        this.model.clear();

        this.createFilters();
        this.setupViewModeDataList();

        this.listenTo(this.collection, 'order-changed', () => {
            this.controlResetButtonVisibility();
        });

        this.wait(
            this.getHelper().processSetupHandlers(this, 'record/search')
        );
    }

    setupViewModeDataList() {
        if (!this.viewModeList) {
            return [];
        }

        let list = [];

        this.viewModeList.forEach(item => {
            let o = {
                name: item,
                title: this.translate(item, 'listViewModes'),
                iconClass: this.viewModeIconClassMap[item]
            };

            list.push(o);
        });

        this.viewModeDataList = list;
    }

    setViewMode(mode, preventLoop, toTriggerEvent) {
        this.viewMode = mode;

        if (this.isRendered()) {
            this.$el.find('[data-action="switchViewMode"]').removeClass('active');
            this.$el.find('[data-action="switchViewMode"][data-name="'+mode+'"]').addClass('active');
        }
        else {
            if (this.isBeingRendered() && !preventLoop) {
                this.once('after:render', () => {
                    this.setViewMode(mode, true);
                });
            }
        }

        if (toTriggerEvent) {
            this.trigger('change-view-mode', mode);
        }
    }

    isLeftDropdown() {
        return this.presetFilterList.length ||
            this.boolFilterList.length ||
            Object.keys(this.advanced || {}).length;
    }

    handleLeftDropdownVisibility() {
        if (this.isLeftDropdown()) {
            this.$leftDropdown.removeClass('hidden');
        }
        else {
            this.$leftDropdown.addClass('hidden');
        }
    }

    createFilters(callback) {
        let i = 0;
        let count = Object.keys(this.advanced || {}).length;

        if (count === 0) {
            if (typeof callback === 'function') {
                callback();
            }
        }

        for (let field in this.advanced) {
            this.createFilter(field, this.advanced[field], () => {
                i++;

                if (i === count) {
                    if (typeof callback === 'function') {
                        callback();
                    }
                }
            });
        }
    }

    events = {
        /** @this SearchView */
        'keydown input[data-name="textFilter"]': function (e) {
            let key = Espo.Utils.getKeyFromKeyEvent(e);

            if (e.code === 'Enter' || key === 'Enter' || key === 'Control+Enter') {
                this.search();

                this.hideApplyFiltersButton();
            }
        },
        /** @this SearchView */
        'focus input[data-name="textFilter"]': function (e) {
            e.currentTarget.select();
        },
        /** @this SearchView */
        'click .advanced-filters-apply-container a[data-action="applyFilters"]': function () {
            this.search();
            this.hideApplyFiltersButton();

            this.$el.find('button.search').focus();
        },
        /** @this SearchView */
        'click button[data-action="search"]': function () {
            this.search();
            this.hideApplyFiltersButton();
        },
        /** @this SearchView */
        'click a[data-action="addFilter"]': function (e) {
            let $target = $(e.currentTarget);
            let name = $target.data('name');

            $target.closest('li').addClass('hidden');

            this.addFilter(name);
        },
        /** @this SearchView */
        'click .advanced-filters a.remove-filter': function (e) {
            let $target = $(e.currentTarget);

            let name = $target.data('name');

            this.removeFilter(name);
        },
        /** @this SearchView */
        'click button[data-action="reset"]': function () {
            this.resetFilters();
        },
        /** @this SearchView */
        'click button[data-action="refresh"]': function () {
            this.refresh();
        },
        /** @this SearchView */
        'click a[data-action="selectPreset"]': function (e) {
            let $target = $(e.currentTarget);

            let presetName = $target.data('name') || null;

            this.selectPreset(presetName);
        },
        /** @this SearchView */
        'click .dropdown-menu a[data-action="savePreset"]': function () {
            this.createView('savePreset', 'views/modals/save-filters', {}, view => {
                view.render();

                this.listenToOnce(view, 'save', (name) => {
                    this.savePreset(name);

                    view.close();

                    this.removeFilters();

                    this.createFilters(() => {
                        this.render();
                    });
                });
            });
        },
        /** @this SearchView */
        'click .dropdown-menu a[data-action="removePreset"]': function () {
            let id = this.presetName;

            this.confirm(this.translate('confirmation', 'messages'), () => {
                this.removePreset(id);
            });
        },
        /** @this SearchView */
        'change .search-row ul.filter-menu input[data-role="boolFilterCheckbox"]': function (e) {
            e.stopPropagation();

            this.search();
            this.manageLabels();
        },
        /** @this SearchView */
        'click [data-action="switchViewMode"]': function (e) {
            let mode = $(e.currentTarget).data('name');

            if (mode === this.viewMode) {
                return;
            }

            this.setViewMode(mode, false, true);
        },
        /** @this SearchView */
        'keyup input.field-filter-quick-search-input': function (e) {
            this.processFieldFilterQuickSearch(e.currentTarget.value);
        },
        /** @this SearchView */
        'keydown input.field-filter-quick-search-input': function (e) {
            if (e.code === 'Enter') {
                this.addFirstFieldFilter();

                return;
            }

            if (e.code === 'Escape') {
                this.closeAddFieldDropdown();
            }
        },
    }

    removeFilter(name) {
        this.$el.find('ul.filter-list li[data-name="' + name + '"]').removeClass('hidden');

        let container = this.getView('filter-' + name).$el.closest('div.filter');

        this.clearView('filter-' + name);

        container.remove();

        delete this.advanced[name];

        this.presetName = this.primary;

        this.updateAddFilterButton();
        this.fetch();
        this.updateSearch();
        this.manageLabels();
        this.handleLeftDropdownVisibility();
        this.controlResetButtonVisibility();

        if (this.isSearchedWithAdvancedFilter) {
            this.hasAdvancedFilter() ?
                this.showApplyFiltersButton() :
                this.showResetFiltersButton();

            this.$applyFilters.focus();

            return;
        }

        if (!this.hasAdvancedFilter()) {
            this.hideApplyFiltersButton();
        }
    }

    addFilter(name) {
        this.advanced[name] = {};

        this.presetName = this.primary;

        this.createFilter(name, {}, view => {
            view.populateDefaults();

            this.fetch();
            this.updateSearch();

            if (view.getFieldView().initialSearchIsNotIdle) {
                this.showApplyFiltersButton();
            }
        });

        this.updateAddFilterButton();
        this.handleLeftDropdownVisibility();

        this.manageLabels();
        this.controlResetButtonVisibility();
    }

    refresh() {
        Espo.Ui.notify(' ... ');

        this.collection.abortLastFetch();
        this.collection.reset();

        this.collection.fetch().then(() => {
            Espo.Ui.notify(false);
        });
    }

    selectPreset(presetName, forceClearAdvancedFilters) {
        let wasPreset = !(this.primary === this.presetName);

        this.presetName = presetName;

        let advanced = this.getPresetData();

        this.primary = this.getPrimaryFilterName();

        let isPreset = !(this.primary === this.presetName);

        if (forceClearAdvancedFilters || wasPreset || isPreset || Object.keys(advanced).length) {
            this.removeFilters();
            this.advanced = advanced;
        }

        this.updateSearch();
        this.manageLabels();

        this.createFilters(() => {
            this.reRender()
                .then(() => {
                    this.$el.find('.filters-button')
                        .get(0).focus({preventScroll: true});
                })
        });

        this.updateCollection();
    }

    removeFilters() {
        this.$advancedFiltersPanel.empty();

        for (let name in this.advanced) {
            this.clearView('filter-' + name);
        }
    }

    resetFilters() {
        this.trigger('reset');

        this.collection.resetOrderToDefault();

        this.textFilter = '';

        this.selectPreset(this.presetName, true);

        this.hideApplyFiltersButton();

        this.trigger('update-ui');
    }

    savePreset(name) {
        let id = 'f' + (Math.floor(Math.random() * 1000001)).toString();

        this.fetch();
        this.updateSearch();

        let presetFilters = this.getPreferences().get('presetFilters') || {};

        if (!(this.scope in presetFilters)) {
            presetFilters[this.scope] = [];
        }

        let data = {
            id: id,
            name: id,
            label: name,
            data: this.advanced,
            primary: this.primary,
        };

        presetFilters[this.scope].push(data);

        this.presetFilterList.push(data);

        this.getPreferences().once('sync', () => {
            this.getPreferences().trigger('update');
            this.updateSearch()
        });

        this.getPreferences().save({'presetFilters': presetFilters}, {patch: true});

        this.presetName = id;
    }

    removePreset(id) {
        let presetFilters = this.getPreferences().get('presetFilters') || {};

        if (!(this.scope in presetFilters)) {
            presetFilters[this.scope] = [];
        }

        let list;

        list = presetFilters[this.scope];

        list.forEach((item, i) => {
            if (item.id === id) {
                list.splice(i, 1);
            }
        });

        list = this.presetFilterList;

        list.forEach((item, i) => {
            if (item.id === id) {
                list.splice(i, 1);
            }
        });

        this.getPreferences().set('presetFilters', presetFilters);
        this.getPreferences().save({patch: true});
        this.getPreferences().trigger('update');

        this.presetName = this.primary;
        this.advanced = {};

        this.removeFilters();

        this.render();
        this.updateSearch();
        this.updateCollection();
    }

    updateAddFilterButton() {
        let $ul = this.$el.find('ul.filter-list');

        if (
            $ul.children()
                .not('.hidden')
                .not('.dropdown-header')
                .not('.quick-search-list-item').length === 0
        ) {
            this.$addFilterButton.addClass('disabled');
        }
        else {
            this.$addFilterButton.removeClass('disabled');
        }

        this.trigger('update-ui');
    }

    afterRender() {
        this.$filtersLabel = this.$el.find('.search-row span.filters-label');
        this.$filtersButton = this.$el.find('.search-row button.filters-button');
        this.$leftDropdown = this.$el.find('div.search-row div.left-dropdown');
        this.$resetButton = this.$el.find('[data-action="reset"]');
        this.$applyFiltersContainer = this.$el.find('.advanced-filters-apply-container');
        this.$applyFilters = this.$applyFiltersContainer.find('[data-action="applyFilters"]');
        /** @type {JQuery} */
        this.$filterList = this.$el.find('ul.filter-list');
        /** @type {JQuery} */
        this.$fieldQuickSearch = this.$filterList.find('input.field-filter-quick-search-input');
        /** @type {JQuery} */
        this.$addFilterButton = this.$el.find('button.add-filter-button');
        /** @type {JQuery} */
        this.$textFilter = this.$el.find('input.text-filter');

        this.updateAddFilterButton();

        this.$advancedFiltersPanel = this.$el.find('.advanced-filters');

        this.manageLabels();
        this.controlResetButtonVisibility();
        this.initQuickSearchUi();
        this.initTextSearchAutocomplete();
    }

    initTextSearchAutocomplete() {
        if (this.textSearchStoringDisabled) {
            return;
        }

        let preventCloseOnBlur = false;

        let options = {
            minChars: 0,
            noCache: true,
            triggerSelectOnValidInput: false,
            beforeRender: $container => {
                $container.addClass('text-search-suggestions');

                $container.off('mousedown');
                $container.on('mousedown', e => {
                    if (e.originalEvent.button !== 0) {
                        return;
                    }

                    preventCloseOnBlur = true;
                    setTimeout(() => preventCloseOnBlur = false, 201);
                });

                $container.find('a[data-action="clearStoredTextSearch"]').on('click', e => {
                    e.stopPropagation();
                    e.preventDefault();

                    let text = e.currentTarget.getAttribute('data-value');

                    this.storedTextSearchHelper.remove(text);

                    setTimeout(() => this.$textFilter.focus(), 205);
                });
            },
            formatResult: item => {
                return $('<span>')
                    .append(
                        $('<a>')
                            .attr('data-action', 'clearStoredTextSearch')
                            .attr('role', 'button')
                            .attr('data-value', item.value)
                            .attr('title', this.translate('Remove'))
                            .html('<span class="fas fa-times fa-sm"></span>')
                            .addClass('pull-right text-soft'),
                        $('<span>')
                            .text(item.value)
                    )
                    .get(0).innerHTML;
            },
            lookup: (text, done) => {
                let suggestions = this.storedTextSearchHelper.match(text, this.autocompleteLimit)
                    .map(item => {
                        return {value: item};
                    });

                done({suggestions: suggestions});
            },
            onSelect: () => {
                this.$textFilter.focus();
                this.$textFilter.autocomplete('hide');
            },
        };

        this.$textFilter.autocomplete(options);

        this.$textFilter.on('blur', () => {
            if (preventCloseOnBlur) {
                return;
            }

            setTimeout(() => this.$textFilter.autocomplete('hide'), 1);
        });

        this.$textFilter.on('focus', () => {
            if (this.$textFilter.val()) {
                this.$textFilter.autocomplete('hide');
            }
        });

        this.once('render', () => this.$textFilter.autocomplete('dispose'));
        this.once('remove', () => this.$textFilter.autocomplete('dispose'));
    }

    initQuickSearchUi() {
        this.$addFilterButton.parent().on('show.bs.dropdown', () => {
            setTimeout(() => {
                this.$fieldQuickSearch.focus();

                let width = this.$fieldQuickSearch.outerWidth();

                this.$fieldQuickSearch.css('minWidth', width);
            }, 1);
        });

        this.$addFilterButton.parent().on('hide.bs.dropdown', () => {
            this.resetFieldFilterQuickSearch();

            this.$fieldQuickSearch.css('minWidth', '');
        });
    }

    manageLabels() {
        this.$el.find('ul.dropdown-menu > li.preset-control').addClass('hidden');

        this.currentFilterLabelList = [];

        this.managePresetFilters();
        this.manageBoolFilters();

        this.$filtersLabel.html(this.currentFilterLabelList.join(' &middot; '));
    }

    /**
     * @private
     * @return {boolean}
     */
    toShowResetButton() {
        if (this.textFilter) {
            return true;
        }

        let presetName = this.presetName || null;
        let primary = this.primary;

        if (!presetName || presetName === primary) {
            if (Object.keys(this.advanced).length) {
                return true;
            }
        }

        if (
            this.collection.orderBy !== this.collection.defaultOrderBy ||
            this.collection.order !== this.collection.defaultOrder
        ) {
            return true;
        }

        return false;
    }

    controlResetButtonVisibility() {
        if (this.toShowResetButton()) {
            this.$resetButton.css('visibility', 'visible');

            return;
        }

        this.$resetButton.css('visibility', 'hidden');
    }

    managePresetFilters() {
        let presetName = this.presetName || null;
        let primary = this.primary;

        this.$el.find('ul.filter-menu a.preset span').remove();

        let filterLabel = this.translate('all', 'presetFilters', this.entityType);
        let filterStyle = 'default';

        if (!presetName && primary) {
            presetName = primary;
        }

        if (presetName && presetName !== primary) {
            this.$advancedFiltersPanel.addClass('hidden');

            let label = null;
            let style = 'default';
            let id = null;

            this.presetFilterList.forEach(item => {
                if (item.name === presetName) {
                    label = item.label || false;
                    style = item.style || 'default';
                    id = item.id;
                }
            });

            label = label || this.translate(this.presetName, 'presetFilters', this.entityType);

            filterLabel = label;
            filterStyle = style;

            if (id) {
                this.$el.find('ul.dropdown-menu > li.divider.preset-control').removeClass('hidden');
                this.$el.find('ul.dropdown-menu > li.preset-control.remove-preset').removeClass('hidden');
            }
        }
        else {
            this.$advancedFiltersPanel.removeClass('hidden');

            if (Object.keys(this.advanced).length !== 0) {
                if (!this.disableSavePreset) {
                    this.$el.find('ul.dropdown-menu > li.divider.preset-control').removeClass('hidden');
                    this.$el.find('ul.dropdown-menu > li.preset-control.save-preset').removeClass('hidden');
                    this.$el.find('ul.dropdown-menu > li.preset-control.remove-preset').addClass('hidden');
                }
            }

            if (primary) {
                let label = this.translate(primary, 'presetFilters', this.entityType);
                let style = this.getPrimaryFilterStyle();

                filterLabel = label;
                filterStyle = style;
            }
        }

        this.currentFilterLabelList.push(filterLabel);

        this.$filtersButton
            .removeClass('btn-default')
            .removeClass('btn-primary')
            .removeClass('btn-danger')
            .removeClass('btn-success')
            .removeClass('btn-info');

        this.$filtersButton.addClass('btn-' + filterStyle);

        presetName = presetName || '';

        this.$el
            .find('ul.filter-menu a.preset[data-name="'+presetName+'"]')
            .prepend('<span class="fas fa-check pull-right"></span>');
    }

    manageBoolFilters() {
        (this.boolFilterList || []).forEach((item) => {
            if (this.bool[item]) {
                let label = this.translate(item, 'boolFilters', this.entityType);

                this.currentFilterLabelList.push(label);
            }
        });
    }

    search() {
        this.fetch();
        this.updateSearch();
        this.updateCollection();
        this.controlResetButtonVisibility();
        this.storeTextSearch();

        this.isSearchedWithAdvancedFilter = this.hasAdvancedFilter();
    }

    hasAdvancedFilter() {
        return Object.keys(this.advanced).length > 0;
    }

    getFilterDataList() {
        let list = [];

        for (let field in this.advanced) {
            list.push({
                key: 'filter-' + field,
                name: field,
            });
        }

        return list;
    }

    updateCollection() {
        this.collection.abortLastFetch();
        this.collection.reset();
        this.collection.where = this.searchManager.getWhere();
        this.collection.offset = 0;

        Espo.Ui.notify(' ... ');

        this.collection.fetch().then(() => {
            Espo.Ui.notify(false);
        });
    }

    getPresetFilterList() {
        let arr = [];

        this.presetFilterList.forEach((item) => {
            if (typeof item == 'string') {
                item = {name: item};
            }

            arr.push(item);
        });

        return arr;
    }

    getPresetData() {
        let data = {};

        this.getPresetFilterList().forEach(item => {
            if (item.name === this.presetName) {
                data = Espo.Utils.clone(item.data || {});
            }
        });

        return data;
    }

    getPrimaryFilterName() {
        let primaryFilterName = null;

        this.getPresetFilterList().forEach(item => {
            if (item.name === this.presetName) {
                if (!('data' in item)) {
                    primaryFilterName = item.name;
                }
                else if (item.primary) {
                    primaryFilterName = item.primary;
                }
            }
        });

        return primaryFilterName;
    }

    getPrimaryFilterStyle() {
        let style = null;

        this.getPresetFilterList().forEach(item => {
            if (item.name === this.primary) {
                style = item.style || 'default';
            }
        });

        return style;
    }

    loadSearchData() {
        let searchData = this.searchManager.get();

        this.textFilter = searchData.textFilter;

        if ('presetName' in searchData) {
            this.presetName = searchData.presetName;
        }

        let primaryIsSet = false;

        if ('primary' in searchData) {
            this.primary = searchData.primary;

            if (!this.presetName) {
                this.presetName = this.primary;
            }

            primaryIsSet = true;
        }

        if (this.presetName) {
            this.advanced = _.extend(Espo.Utils.clone(this.getPresetData()), searchData.advanced);

            if (!primaryIsSet) {
                this.primary = this.getPrimaryFilterName();
            }
        }
        else {
            this.advanced = Espo.Utils.clone(searchData.advanced);
        }

        this.bool = searchData.bool;
    }

    /**
     * @callback SearchView~createFilterCallback
     * @param {module:views/search/filter} view
     */

    /**
     * @param {string} name
     * @param {Object.<string, *>} params
     * @param {SearchView~createFilterCallback} callback
     * @param {boolean} [noRender]
     */
    createFilter(name, params, callback, noRender) {
        params = params || {};

        let rendered = false;

        if (this.isRendered()) {
            rendered = true;

            this.$advancedFiltersPanel.append(
                '<div data-name="'+name+'" class="filter filter-' + name + '" />'
            );
        }

        this.createView('filter-' + name, 'views/search/filter', {
            name: name,
            model: this.model,
            params: params,
            selector: '.filter[data-name="' + name + '"]',
        }, view => {
            if (typeof callback === 'function') {
                view.once('after:render', () => {
                    callback(view);
                });
            }

            if (rendered && !noRender) {
                view.render();
            }

            this.listenTo(view, 'change', () => {
                let toShowApply = this.isSearchedWithAdvancedFilter;

                if (!toShowApply) {
                    let data = view.getView('field').fetchSearch();

                    if (data) {
                        toShowApply = true;
                    }
                }

                if (!toShowApply) {
                    return;
                }

                this.showApplyFiltersButton();
            });

            this.listenTo(view, 'search', () => {
                this.search();
                this.hideApplyFiltersButton();
            });
        });
    }

    fetch() {
        this.textFilter = (this.$el.find('input[data-name="textFilter"]').val() || '').trim();

        this.bool = {};

        this.boolFilterList.forEach(name => {
            this.bool[name] = this.$el
                .find('input[data-name="' + name + '"][data-role="boolFilterCheckbox"]')
                .prop('checked');
        });

        for (let field in this.advanced) {
            let view = this.getView('filter-' + field).getView('field');

            this.advanced[field] = view.fetchSearch();

            view.searchParams = this.advanced[field];
        }
    }

    updateSearch() {
        this.searchManager.set({
            textFilter: this.textFilter,
            advanced: this.advanced,
            bool: this.bool,
            presetName: this.presetName,
            primary: this.primary,
        });
    }

    getFilterFieldDataList() {
        let defs = [];

        for (let i in this.fieldFilterList) {
            let field = this.fieldFilterList[i];

            let o = {
                name: field,
                checked: (field in this.advanced),
                label: this.fieldFilterTranslations[field] || field,
            };

            defs.push(o);
        }

        return defs;
    }

    showResetFiltersButton() {
        this.toShowApplyFiltersButton = true;
        this.toShowResetFiltersText = true;

        this.$applyFiltersContainer.removeClass('hidden');

        this.$applyFiltersContainer.find('.text-apply').addClass('hidden');
        this.$applyFiltersContainer.find('.text-reset').removeClass('hidden');
    }

    showApplyFiltersButton() {
        this.toShowApplyFiltersButton = true;
        this.toShowResetFiltersText = false;

        this.$applyFiltersContainer.removeClass('hidden');

        this.$applyFiltersContainer.find('.text-reset').addClass('hidden');
        this.$applyFiltersContainer.find('.text-apply').removeClass('hidden');
    }

    hideApplyFiltersButton() {
        this.toShowApplyFiltersButton = false;
        this.toShowResetFiltersText = false;

        this.$applyFiltersContainer.addClass('hidden');
    }

    selectPreviousPreset() {
        let list = Espo.Utils.clone(this.getPresetFilterList());

        list.unshift({name: null});

        if (list.length === 1) {
            return;
        }

        let index = list.findIndex(item => item.name === this.presetName) - 1;

        if (index < 0) {
            return;
        }

        let preset = list[index];

        this.selectPreset(preset.name);
    }

    selectNextPreset() {
        let list = Espo.Utils.clone(this.getPresetFilterList());

        list.unshift({name: null});

        if (list.length === 1) {
            return;
        }

        let index = list.findIndex(item => item.name === this.presetName) + 1;

        if (index >= list.length) {
            return;
        }

        let preset = list[index];

        this.selectPreset(preset.name);
    }

    /**
     * @private
     * @param {string} text
     */
    processFieldFilterQuickSearch(text) {
        text = text.trim();
        text = text.toLowerCase();

        /** @type {JQuery} */
        let $li = this.$filterList.find('li.filter-item');

        if (text === '') {
            $li.removeClass('search-hidden');

            return;
        }

        $li.addClass('search-hidden');

        this.fieldFilterList.forEach(field => {
            let label = this.fieldFilterTranslations[field] || field;
            label = label.toLowerCase();

            let wordList = label.split(' ');

            let matched = label.indexOf(text) === 0;

            if (!matched) {
                matched = wordList
                    .filter(word => word.length > 3 && word.indexOf(text) === 0)
                    .length > 0;
            }

            if (matched) {
                $li.filter(`[data-name="${field}"]`).removeClass('search-hidden');
            }
        });
    }

    resetFieldFilterQuickSearch() {
        this.$fieldQuickSearch.val('');
        this.$filterList.find('li.filter-item').removeClass('search-hidden');
    }

    addFirstFieldFilter() {
        let $first = this.$filterList.find('li.filter-item:not(.hidden):not(.search-hidden)').first();

        if (!$first.length) {
            return;
        }

        let name = $first.attr('data-name');

        $first.addClass('hidden');

        this.closeAddFieldDropdown();
        this.addFilter(name);
        this.resetFieldFilterQuickSearch();
    }

    closeAddFieldDropdown() {
        this.$addFilterButton.parent()
            .find('[data-toggle="dropdown"]')
            .dropdown('toggle');
    }

    storeTextSearch() {
        if (!this.textFilter) {
            return;
        }

        if (this.textSearchStoringDisabled) {
            return;
        }

        this.storedTextSearchHelper.store(this.textFilter);
    }
}

export default SearchView;
PK]�\����6views/group-email-folder/record/row-actions/default.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email-folder/record/row-actions/default', ['views/record/row-actions/default'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);
        },

        getActionList: function () {
            var list = Dep.prototype.getActionList.call(this);

            if (this.options.acl.edit) {
                list.unshift({
                    action: 'moveDown',
                    label: 'Move Down',
                    data: {
                        id: this.model.id,
                    },
                });

                list.unshift({
                    action: 'moveUp',
                    label: 'Move Up',
                    data: {
                        id: this.model.id,
                    },
                });
            }

            return list;
        },
    });
});
PK]���m
m
'views/group-email-folder/record/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/group-email-folder/record/list', ['views/record/list'], function (Dep) {

    return Dep.extend({

        rowActionsView: 'views/email-folder/record/row-actions/default',

        actionMoveUp: function (data) {
            let model = this.collection.get(data.id);

            if (!model) {
                return;
            }

            let index = this.collection.indexOf(model);

            if (index === 0) {
                return;
            }

            Espo.Ajax.postRequest('GroupEmailFolder/action/moveUp', {id: model.id})
                .then(() => {
                    this.collection.fetch();
                });
        },

        actionMoveDown: function (data) {
            let model = this.collection.get(data.id);

            if (!model) {
                return;
            }

            let index = this.collection.indexOf(model);

            if ((index === this.collection.length - 1) && (this.collection.length === this.collection.total)) {
                return;
            }

            Espo.Ajax.postRequest('GroupEmailFolder/action/moveDown', {id: model.id})
                .then(() => {
                    this.collection.fetch();
                });
        },
    });
});
PK]���tt"views/user-security/modals/totp.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/user-security/modals/totp', ['views/modal', 'model'], function (Dep, Model) {

    let QRCode;

    return Dep.extend({

        template: 'user-security/modals/totp',

        className: 'dialog dialog-record',

        shortcutKeys: {
            'Control+Enter': 'apply',
        },

        setup: function () {
            this.buttonList = [
                {
                    name: 'apply',
                    label: 'Apply',
                    style: 'danger',
                },
                {
                    name: 'cancel',
                    label: 'Cancel',
                },
            ];

            this.headerHtml = '&nbsp';

            var model = new Model();

            model.name = 'UserSecurity';

            this.wait(
                Espo.Ajax
                    .postRequest('UserSecurity/action/getTwoFactorUserSetupData', {
                        id: this.model.id,
                        password: this.model.get('password'),
                        auth2FAMethod: this.model.get('auth2FAMethod'),
                        reset: this.options.reset,
                    })
                    .then(data => {
                        this.label = data.label;
                        this.secret = data.auth2FATotpSecret;

                        model.set('secret', data.auth2FATotpSecret);
                    })
            );

            model.setDefs({
                fields: {
                    'code': {
                        type: 'varchar',
                        required: true,
                        maxLength: 7,
                    },
                    'secret': {
                        type: 'varchar',
                        readOnly: true,
                    },
                }
            });

            this.createView('record', 'views/record/edit-for-modal', {
                scope: 'None',
                selector: '.record',
                model: model,
                detailLayout: [
                    {
                        rows: [
                            [
                                {
                                    name: 'secret',
                                    labelText: this.translate('Secret', 'labels', 'User'),
                                },
                                false
                            ],
                            [
                                {
                                    name: 'code',
                                    labelText: this.translate('Code', 'labels', 'User'),
                                },
                                false
                            ]
                        ]
                    }
                ],
            });

            Espo.loader.requirePromise('lib!qrcodejs').then(lib => {
                QRCode = lib;
            })
        },

        afterRender: function () {
            new QRCode(this.$el.find('.qrcode').get(0), {
                text: 'otpauth://totp/' + this.label + '?secret=' + this.secret,
                width: 256,
                height: 256,
                colorDark : '#000000',
                colorLight : '#ffffff',
                correctLevel : QRCode.CorrectLevel.H,
            });
        },

        actionApply: function () {
            var data = this.getView('record').processFetch();

            if (!data) {
                return;
            }

            this.model.set('code', data.code);

            this.hideButton('apply');
            this.hideButton('cancel');

            Espo.Ui.notify(this.translate('pleaseWait', 'messages'));

            this.model
                .save()
                .then(() => {
                    Espo.Ui.notify(false);

                    this.trigger('done');
                })
                .catch(() => {
                    this.showButton('apply');
                    this.showButton('cancel');
                });
        },

    });
});
PK]ˏ����,views/user-security/modals/two-factor-sms.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/user-security/modals/two-factor-sms',
    ['views/modal', 'model'],
    function (Dep, Model) {

    return Dep.extend({

        template: 'user-security/modals/two-factor-sms',

        className: 'dialog dialog-record',

        shortcutKeys: {
            'Control+Enter': 'apply',
        },

        events: {
            'click [data-action="sendCode"]': function () {
                this.actionSendCode();
            },
        },

        setup: function () {
            this.buttonList = [
                {
                    name: 'apply',
                    label: 'Apply',
                    style: 'danger',
                    hidden: true,
                },
                {
                    name: 'cancel',
                    label: 'Cancel',
                },
            ];

            this.headerHtml = '&nbsp';

            let codeLength = this.getConfig().get('auth2FASmsCodeLength') || 7;

            let model = new Model();

            model.name = 'UserSecurity';

            model.set('phoneNumber', null);

            model.setDefs({
                fields: {
                    'code': {
                        type: 'varchar',
                        required: true,
                        maxLength: codeLength,
                    },
                    'phoneNumber': {
                        type: 'enum',
                        required: true,
                    },
                }
            });

            this.internalModel = model;

            this.wait(
                Espo.Ajax
                    .postRequest('UserSecurity/action/getTwoFactorUserSetupData', {
                        id: this.model.id,
                        password: this.model.get('password'),
                        auth2FAMethod: this.model.get('auth2FAMethod'),
                        reset: this.options.reset,
                    })
                    .then(data => {
                        this.phoneNumberList = data.phoneNumberList;

                        this.createView('record', 'views/record/edit-for-modal', {
                            scope: 'None',
                            selector: '.record',
                            model: model,
                            detailLayout: [
                                {
                                    rows: [
                                        [
                                            {
                                                name: 'phoneNumber',
                                                labelText: this.translate('phoneNumber', 'fields', 'User'),
                                            },
                                            false
                                        ],
                                        [
                                            {
                                                name: 'code',
                                                labelText: this.translate('Code', 'labels', 'User'),
                                            },
                                            false
                                        ],
                                    ]
                                }
                            ],
                        }, view => {
                            view.setFieldOptionList('phoneNumber', this.phoneNumberList);

                            if (this.phoneNumberList.length) {
                                model.set('phoneNumber', this.phoneNumberList[0]);
                            }

                            view.hideField('code');
                        });
                    })
            );
        },

        afterRender: function () {
            this.$sendCode = this.$el.find('[data-action="sendCode"]');

            this.$pInfo = this.$el.find('p.p-info');
            this.$pButton = this.$el.find('p.p-button');
            this.$pInfoAfter = this.$el.find('p.p-info-after');
        },

        actionSendCode: function () {
            this.$sendCode.attr('disabled', 'disabled').addClass('disabled');

            Espo.Ajax
                .postRequest('TwoFactorSms/action/sendCode', {
                    id: this.model.id,
                    phoneNumber: this.internalModel.get('phoneNumber'),
                })
                .then(() => {
                    this.showButton('apply');

                    this.$pInfo.addClass('hidden');
                    this.$pButton.addClass('hidden');
                    this.$pInfoAfter.removeClass('hidden');

                    this.getView('record').setFieldReadOnly('phoneNumber');
                    this.getView('record').showField('code');
                })
                .catch(() => {
                    this.$sendCode.removeAttr('disabled').removeClass('disabled');
                });
        },

        actionApply: function () {
            let data = this.getView('record').processFetch();

            if (!data) {
                return;
            }

            this.model.set('code', data.code);

            this.hideButton('apply');
            this.hideButton('cancel');

            Espo.Ui.notify(this.translate('pleaseWait', 'messages'));

            this.model
                .save()
                .then(() => {
                    Espo.Ui.notify(false);

                    this.trigger('done');
                })
                .catch(() => {
                    this.showButton('apply');
                    this.showButton('cancel');
                });
        },

    });
});
PK]٤���.views/user-security/modals/two-factor-email.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/user-security/modals/two-factor-email',
    ['views/modal', 'model'],
    function (Dep, Model) {

    return Dep.extend({

        template: 'user-security/modals/two-factor-email',

        className: 'dialog dialog-record',

        shortcutKeys: {
            'Control+Enter': 'apply',
        },

        events: {
            'click [data-action="sendCode"]': function () {
                this.actionSendCode();
            },
        },

        setup: function () {
            this.buttonList = [
                {
                    name: 'apply',
                    label: 'Apply',
                    style: 'danger',
                    hidden: true,
                },
                {
                    name: 'cancel',
                    label: 'Cancel',
                },
            ];

            this.headerHtml = '&nbsp';

            let codeLength = this.getConfig().get('auth2FAEmailCodeLength') || 7;

            let model = new Model();
            model.entityType = model.name = 'UserSecurity';

            model.set('emailAddress', null);

            model.setDefs({
                fields: {
                    'code': {
                        type: 'varchar',
                        required: true,
                        maxLength: codeLength,
                    },
                    'emailAddress': {
                        type: 'enum',
                        required: true,
                    },
                }
            });

            this.internalModel = model;

            this.wait(
                Espo.Ajax
                    .postRequest('UserSecurity/action/getTwoFactorUserSetupData', {
                        id: this.model.id,
                        password: this.model.get('password'),
                        auth2FAMethod: this.model.get('auth2FAMethod'),
                        reset: this.options.reset,
                    })
                    .then(data => {
                        this.emailAddressList = data.emailAddressList;

                        this.createView('record', 'views/record/edit-for-modal', {
                            scope: 'None',
                            selector: '.record',
                            model: model,
                            detailLayout: [
                                {
                                    rows: [
                                        [
                                            {
                                                name: 'emailAddress',
                                                labelText: this.translate('emailAddress', 'fields', 'User'),
                                            },
                                            false
                                        ],
                                        [
                                            {
                                                name: 'code',
                                                labelText: this.translate('Code', 'labels', 'User'),
                                            },
                                            false
                                        ],
                                    ]
                                }
                            ],
                        }, view => {
                            view.setFieldOptionList('emailAddress', this.emailAddressList);

                            if (this.emailAddressList.length) {
                                model.set('emailAddress', this.emailAddressList[0]);
                            }

                            view.hideField('code');
                        });
                    })
            );
        },

        afterRender: function () {
            this.$sendCode = this.$el.find('[data-action="sendCode"]');

            this.$pInfo = this.$el.find('p.p-info');
            this.$pButton = this.$el.find('p.p-button');
            this.$pInfoAfter = this.$el.find('p.p-info-after');
        },

        actionSendCode: function () {
            this.$sendCode.attr('disabled', 'disabled').addClass('disabled');

            Espo.Ajax
                .postRequest('TwoFactorEmail/action/sendCode', {
                    id: this.model.id,
                    emailAddress: this.internalModel.get('emailAddress'),
                })
                .then(() => {
                    this.showButton('apply');

                    this.$pInfo.addClass('hidden');
                    this.$pButton.addClass('hidden');
                    this.$pInfoAfter.removeClass('hidden');

                    this.getView('record').setFieldReadOnly('emailAddress');
                    this.getView('record').showField('code');
                })
                .catch(() => {
                    this.$sendCode.removeAttr('disabled').removeClass('disabled');
                });
        },

        actionApply: function () {
            let data = this.getView('record').processFetch();

            if (!data) {
                return;
            }

            this.model.set('code', data.code);

            this.hideButton('apply');
            this.hideButton('cancel');

            Espo.Ui.notify(this.translate('pleaseWait', 'messages'));

            this.model
                .save()
                .then(() => {
                    Espo.Ui.notify(false);

                    this.trigger('done');
                })
                .catch(() => {
                    this.showButton('apply');
                    this.showButton('cancel');
                });
        },

    });
});
PK]
~2��W�Wviews/list-with-categories.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/list-with-categories */

import ListView from 'views/list';

class ListWithCategories extends ListView {

    template = 'list-with-categories'

    quickCreate = true
    storeViewAfterCreate = true
    storeViewAfterUpdate = true
    /** @type {string|null} */
    currentCategoryId = null
    currentCategoryName = ''
    /** @type {string|null} */
    categoryScope = null
    categoryField = 'category'
    categoryFilterType = 'inCategory'
    isExpanded = false
    hasExpandedToggler = true
    expandedTogglerDisabled = false
    keepCurrentRootUrl = true
    hasNavigationPanel = false
    /** @private */
    nestedCollectionIsBeingFetched = false
    /**
     * @type {module:collections/tree}
     * @private
     */
    nestedCategoriesCollection

    data() {
        let data = {};

        data.hasTree = (this.isExpanded || this.hasNavigationPanel) && !this.categoriesDisabled;
        data.hasNestedCategories = !this.isExpanded;
        data.fallback = !data.hasTree && !data.hasNestedCategories;

        return data;
    }

    setup() {
        super.setup();

        if (!this.categoryScope) {
            this.categoryScope = this.scope + 'Category';
        }

        this.showEditLink =
            this.getAcl().check(this.categoryScope, 'edit') ||
            this.getAcl().check(this.categoryScope, 'create');

        let isExpandedByDefault = this.getMetadata()
            .get(['clientDefs', this.categoryScope, 'isExpandedByDefault']) || false;

        if (isExpandedByDefault) {
            this.isExpanded = true;
        }

        let isCollapsedByDefault = this.getMetadata()
            .get(['clientDefs', this.categoryScope, 'isCollapsedByDefault']) || false;

        if (isCollapsedByDefault) {
            this.isExpanded = false;
        }

        this.categoriesDisabled =
            this.categoriesDisabled ||
            this.getMetadata().get(['scopes', this.categoryScope, 'disabled']) ||
            !this.getAcl().checkScope(this.categoryScope);

        if (this.categoriesDisabled) {
            this.isExpanded = true;
            this.hasExpandedToggler = false;
            this.hasNavigationPanel = false;
        }
        else if (!this.expandedTogglerDisabled) {
            if (!this.getUser().isPortal()) {
                if (this.hasIsExpandedStoredValue()) {
                    this.isExpanded = this.getIsExpandedStoredValue();
                }
            }

            if (this.getUser().isPortal()) {
                this.hasExpandedToggler = false;
                this.isExpanded = false;
            }
        }

        if (this.hasNavigationPanelStoredValue()) {
            this.hasNavigationPanel = this.getNavigationPanelStoredValue();
        }

        let params = this.options.params || {};

        if ('categoryId' in params) {
            this.currentCategoryId = params.categoryId;
        }

        this.applyCategoryToCollection();

        this.listenTo(this.collection, 'sync', (c, d, o) => {
            if (o && o.openCategory) {
                return;
            }

            this.controlListVisibility();
        });
    }

    prepareCreateReturnDispatchParams(params) {
        if (this.currentCategoryId) {
            params.options.categoryId = this.currentCategoryId;
            params.options.categoryName = this.currentCategoryName;
        }
    }

    /**
     * @inheritDoc
     */
    setupReuse(params) {
        this.applyRoutingParams(params);
    }

    applyRoutingParams(params) {
        if (!this.isExpanded) {
            if ('categoryId' in params) {
                if (params.categoryId !== this.currentCategoryId) {
                    this.openCategory(params.categoryId, params.categoryName);
                }
            }

            this.selectCurrentCategory();
        }
    }

    hasTextFilter() {
        if (this.collection.where) {
            for (let i = 0; i < this.collection.where.length; i++) {
                if (this.collection.where[i].type === 'textFilter') {
                    return true;
                }
            }
        }

        if (this.collection.data && this.collection.data.textFilter) {
            return true;
        }

        return false;
    }

    hasNavigationPanelStoredValue() {
        return this.getStorage().has('state', 'categories-navigation-panel-' + this.scope);
    }

    getNavigationPanelStoredValue() {
        let value = this.getStorage().get('state', 'categories-navigation-panel-' + this.scope);

        return value === 'true' || value === true;
    }

    setNavigationPanelStoredValue(value) {
        return this.getStorage().set('state', 'categories-navigation-panel-' + this.scope, value);
    }

    hasIsExpandedStoredValue() {
        return this.getStorage().has('state', 'categories-expanded-' + this.scope);
    }

    getIsExpandedStoredValue() {
        let value = this.getStorage().get('state', 'categories-expanded-' + this.scope);

        return value === 'true' || value === true ;
    }

    setIsExpandedStoredValue(value) {
        return this.getStorage().set('state', 'categories-expanded-' + this.scope, value);
    }

    afterRender() {
        this.$nestedCategoriesContainer = this.$el.find('.nested-categories-container');
        this.$listContainer = this.$el.find('.list-container');

        if (!this.hasView('list')) {
            if (!this.isExpanded) {
                this.hideListContainer();
            }

            this.loadList();
        }
        else {
            this.controlListVisibility();
        }

        if (
            !this.categoriesDisabled &&
            (this.isExpanded || this.hasNavigationPanel) &&
            !this.hasView('categories')
        ) {
            this.loadCategories();
        }

        if (!this.isExpanded && !this.hasView('nestedCategories')) {
            this.loadNestedCategories();
        }

        this.$el.focus();
    }

    // noinspection JSUnusedGlobalSymbols
    actionExpand() {
        this.isExpanded = true;

        this.setIsExpandedStoredValue(true);

        this.applyCategoryToCollection();

        this.clearView('nestedCategories');
        this.clearView('categories');

        this.getRouter().navigate('#' + this.scope);
        this.updateLastUrl();

        this.nestedCategoriesCollection = null;

        this.reRender();

        this.$listContainer.empty();

        this.collection.fetch();
    }

    // noinspection JSUnusedGlobalSymbols
    actionCollapse() {
        this.isExpanded = false;
        this.setIsExpandedStoredValue(false);

        this.applyCategoryToCollection();
        this.applyCategoryToNestedCategoriesCollection();

        this.clearView('categories');

        this.navigateToCurrentCategory();

        this.reRender();

        this.$listContainer.empty();

        this.collection.fetch();
    }

    // noinspection JSUnusedGlobalSymbols
    actionOpenCategory(data) {
        this.openCategory(data.id || null, data.name);

        this.selectCurrentCategory();
        this.navigateToCurrentCategory();
    }

    navigateToCurrentCategory() {
        if (!this.isExpanded) {
            if (this.currentCategoryId) {
                this.getRouter().navigate('#' + this.scope + '/list/categoryId=' + this.currentCategoryId);
            }
            else {
                this.getRouter().navigate('#' + this.scope);
            }
        }
        else {
            this.getRouter().navigate('#' + this.scope);
        }

        this.updateLastUrl();
    }

    selectCurrentCategory() {
        let categoriesView = this.getCategoriesView();

        if (categoriesView) {
            categoriesView.setSelected(this.currentCategoryId);
            categoriesView.reRender();
        }
    }

    openCategory(id, name) {
        this.getNestedCategoriesView().isLoading = true;
        this.getNestedCategoriesView().reRender();
        this.getNestedCategoriesView().isLoading = false;

        this.nestedCategoriesCollection.reset();
        this.collection.reset();

        this.$listContainer.empty();

        this.currentCategoryId = id;
        this.currentCategoryName = name || id;

        this.applyCategoryToNestedCategoriesCollection();
        this.applyCategoryToCollection();

        this.collection.abortLastFetch();

        if (this.nestedCategoriesCollection) {
            this.nestedCategoriesCollection.abortLastFetch();

            this.hideListContainer();
            this.$nestedCategoriesContainer.addClass('hidden');

            Espo.Ui.notify(' ... ');

            Promise
                .all([
                    this.nestedCategoriesCollection.fetch().then(() => this.updateHeader()),
                    this.collection.fetch({openCategory: true})
                ])
                .then(() => {
                    Espo.Ui.notify(false);

                    this.controlNestedCategoriesVisibility();
                    this.controlListVisibility();
                });

            return;
        }

        this.collection.fetch()
            .then(() => {
                Espo.Ui.notify(false);
            });
    }

    controlListVisibility() {
        if (this.isExpanded) {
            this.showListContainer();

            return;
        }

        if (this.nestedCollectionIsBeingFetched) {
            return;
        }

        if (
            !this.collection.models.length &&
            this.nestedCategoriesCollection &&
            this.nestedCategoriesCollection.models.length &&
            !this.hasTextFilter()
        ) {
            this.hideListContainer();

            return;
        }

        this.showListContainer();
    }

    controlNestedCategoriesVisibility() {
        this.$nestedCategoriesContainer.removeClass('hidden');
    }

    getTreeCollection(callback) {
        this.getCollectionFactory().create(this.categoryScope)
            .then(collection => {
                collection.url = collection.entityType + '/action/listTree';
                collection.setOrder(null, null);

                this.collection.treeCollection = collection;

                collection.fetch()
                    .then(() => callback.call(this, collection));
            });
    }

    applyCategoryToNestedCategoriesCollection() {
        if (!this.nestedCategoriesCollection) {
            return;
        }

        this.nestedCategoriesCollection.parentId = this.currentCategoryId;
        this.nestedCategoriesCollection.currentCategoryId = this.currentCategoryId;
        this.nestedCategoriesCollection.currentCategoryName = this.currentCategoryName || this.currentCategoryId;
        this.nestedCategoriesCollection.where = [];
    }

    getNestedCategoriesCollection(callback) {
        this.getCollectionFactory().create(this.categoryScope, collection => {
            this.nestedCategoriesCollection = collection;

            collection.setOrder(null, null);

            collection.url = collection.entityType + '/action/listTree';
            collection.maxDepth = null;
            collection.data.checkIfEmpty = true;

            if (!this.getAcl().checkScope(this.scope, 'create')) {
                collection.data.onlyNotEmpty = true;
            }

            this.applyCategoryToNestedCategoriesCollection();

            this.nestedCollectionIsBeingFetched = true;

            collection
                .fetch()
                .then(() => {
                    this.nestedCollectionIsBeingFetched = false;

                    this.controlNestedCategoriesVisibility();
                    this.controlListVisibility();

                    this.updateHeader();

                    callback.call(this, collection);
                });
        });
    }

    /**
     * @return {module:views/record/list-nested-categories}
     */
    getNestedCategoriesView() {
        return /** @type module:views/record/list-nested-categories */this.getView('nestedCategories');
    }

    /**
     * @return {module:views/record/list-tree}
     */
    getCategoriesView() {
        return /** @type module:views/record/list-tree */this.getView('categories');
    }

    loadNestedCategories() {
        this.getNestedCategoriesCollection(collection => {
            this.createView('nestedCategories', 'views/record/list-nested-categories', {
                collection: collection,
                selector: '.nested-categories-container',
                showEditLink: this.showEditLink,
                isExpanded: this.isExpanded,
                hasExpandedToggler: this.hasExpandedToggler,
                hasNavigationPanel: this.hasNavigationPanel,
                subjectEntityType: this.collection.entityType,
            }, view => {
                view.render();
            });
        });
    }

    loadCategories() {
        this.getTreeCollection(collection => {
            this.createView('categories', 'views/record/list-tree', {
                collection: collection,
                selector: '.categories-container',
                selectable: true,
                showRoot: true,
                rootName: this.translate(this.scope, 'scopeNamesPlural'),
                buttonsDisabled: true,
                checkboxes: false,
                showEditLink: this.showEditLink,
                isExpanded: this.isExpanded,
                hasExpandedToggler: this.hasExpandedToggler,
                menuDisabled: !this.isExpanded && this.hasNavigationPanel,
                readOnly: true,
            }, view => {
                if (this.currentCategoryId) {
                    view.setSelected(this.currentCategoryId);
                }

                view.render();

                this.listenTo(view, 'select', model => {
                    if (!this.isExpanded) {
                        let id = null;
                        let name = null;

                        if (model && model.id) {
                            id = model.id;
                            name = model.get('name');
                        }

                        this.openCategory(id, name);
                        this.navigateToCurrentCategory();

                        return;
                    }

                    this.currentCategoryId = null;
                    this.currentCategoryName = '';

                    if (model && model.id) {
                        this.currentCategoryId = model.id;
                        this.currentCategoryName = model.get('name');
                    }

                    this.applyCategoryToCollection();

                    this.collection.abortLastFetch();

                    Espo.Ui.notify(' ... ');

                    this.collection
                        .fetch()
                        .then(() => Espo.Ui.notify(false));
                });
            });

        });
    }

    applyCategoryToCollection() {
        this.collection.whereFunction = () => {
            let filter;
            let isExpanded = this.isExpanded;

            if (!isExpanded && !this.hasTextFilter()) {
                if (this.isCategoryMultiple()) {
                    if (this.currentCategoryId) {
                        filter = {
                            attribute: this.categoryField,
                            type: 'linkedWith',
                            value: [this.currentCategoryId]
                        };
                    }
                    else {
                        filter = {
                            attribute: this.categoryField,
                            type: 'isNotLinked'
                        };
                    }
                }
                else {
                    if (this.currentCategoryId) {
                        filter = {
                            attribute: this.categoryField + 'Id',
                            type: 'equals',
                            value: this.currentCategoryId
                        };
                    }
                    else {
                        filter = {
                            attribute: this.categoryField + 'Id',
                            type: 'isNull'
                        };
                    }
                }
            }
            else {
                if (this.currentCategoryId) {
                    filter = {
                        attribute: this.categoryField,
                        type: this.categoryFilterType,
                        value: this.currentCategoryId,
                    };
                }
            }

            if (filter) {
                return [filter];
            }
        };
    }

    isCategoryMultiple() {
        return this.getMetadata()
            .get(['entityDefs', this.scope, 'fields', this.categoryField, 'type']) === 'linkMultiple';
    }

    getCreateAttributes() {
        let data;

        if (this.isCategoryMultiple()) {
            if (this.currentCategoryId) {
                let names = {};

                names[this.currentCategoryId] = this.getCurrentCategoryName();

                data = {};

                let idsAttribute = this.categoryField + 'Ids';
                let namesAttribute = this.categoryField + 'Names';

                data[idsAttribute] = [this.currentCategoryId];
                data[namesAttribute] = names;

                return data;
            }

            return null;
        }

        let idAttribute = this.categoryField + 'Id';
        let nameAttribute = this.categoryField + 'Name';

        data = {};

        data[idAttribute] = this.currentCategoryId;
        data[nameAttribute] = this.getCurrentCategoryName();

        return data;
    }

    getCurrentCategoryName() {
        if (this.currentCategoryName) {
            return this.currentCategoryName;
        }

        if (
            this.nestedCategoriesCollection &&
            this.nestedCategoriesCollection.categoryData &&
            this.nestedCategoriesCollection.categoryData.name
        ) {
            return this.nestedCategoriesCollection.categoryData.name;
        }

        return this.currentCategoryId;
    }

    // noinspection JSUnusedGlobalSymbols
    actionManageCategories() {
        this.clearView('categories');
        this.clearView('nestedCategories');

        this.getRouter().navigate('#' + this.categoryScope, {trigger: true});
    }

    getHeader() {
        if (!this.nestedCategoriesCollection) {
            return super.getHeader();
        }

        let path = this.nestedCategoriesCollection.path;

        if (!path || path.length === 0) {
            return super.getHeader();
        }

        let rootUrl = '#' + this.scope;

        let $root = $('<a>')
            .attr('href', rootUrl)
            .addClass('action')
            .text(this.translate(this.scope, 'scopeNamesPlural'))
            .addClass('action')
            .attr('data-action', 'openCategory');

        let list = [$root];

        let currentName = this.nestedCategoriesCollection.categoryData.name;
        let upperId = this.nestedCategoriesCollection.categoryData.upperId;
        let upperName = this.nestedCategoriesCollection.categoryData.upperName;

        if (path.length > 2) {
            list.push('...');
        }

        if (upperId) {
            let url = rootUrl + '/' + 'list/categoryId=' + this.escapeString(upperId);

            let $folder = $('<a>')
                .attr('href', url)
                .text(upperName)
                .addClass('action')
                .attr('data-action', 'openCategory')
                .attr('data-id', upperId)
                .attr('data-name', upperName);

            list.push($folder);
        }

        let $last = $('<span>').text(currentName);

        list.push($last);

        return this.buildHeaderHtml(list);
    }

    updateHeader() {
        this.getView('header').reRender();
    }

    hideListContainer() {
        this.$listContainer.addClass('hidden');
    }

    showListContainer() {
        this.$listContainer.removeClass('hidden');
    }

    // noinspection JSUnusedGlobalSymbols
    actionToggleNavigationPanel() {
        let value = !this.hasNavigationPanel;

        this.hasNavigationPanel = value;

        this.setNavigationPanelStoredValue(value);

        this.reRender().then(() => {
            this.loadNestedCategories();
        });
    }
}

export default ListWithCategories;
PK]���;views/working-time-calendar/fields/time-ranges/item-edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/working-time-calendar/fields/time-ranges/item-edit', ['view', 'lib!moment'], function (Dep, moment) {

    return Dep.extend({

        // language=Handlebars
        templateContent: `
            <div class="row">
                <div class="start-container col-xs-5">
                    <input
                        class="form-control"
                        type="text"
                        data-name="start"
                        value="{{start}}"
                        autocomplete="espo-start"
                        spellcheck="false"
                    >
                </div>
                <div class="start-container col-xs-1 center-align">
                    <span class="field-row-text-item">&nbsp;–&nbsp;</span>
                </div>
                <div class="end-container col-xs-5">
                    <input
                        class="form-control"
                        type="text"
                        data-name="end"
                        value="{{end}}"
                        autocomplete="espo-end"
                        spellcheck="false"
                    >
                </div>
                <div class="col-xs-1 center-align">
                    <a
                        role="button"
                        tabindex="0"
                        class="remove-item field-row-text-item"
                        data-key="{{key}}"
                        title="{{translate 'Remove'}}"
                    ><span class="fas fa-times"></span></a>
                </div>
            </div>
        `,

        timeFormatMap: {
            'HH:mm': 'H:i',
            'hh:mm A': 'h:i A',
            'hh:mm a': 'h:i a',
            'hh:mmA': 'h:iA',
            'hh:mma': 'h:ia',
        },

        minuteStep: 30,

        data: function () {
            let data = {};

            data.start = this.convertTimeToDisplay(this.value[0]);
            data.end = this.convertTimeToDisplay(this.value[1]);

            data.key = this.key;

            return data;
        },

        setup: function () {
            this.value = this.options.value || [null, null];
            this.key = this.options.key;
        },

        convertTimeToDisplay: function (value) {
            if (!value) {
                return '';
            }

            let m = moment(value, 'HH:mm');

            if (!m.isValid()) {
                return '';
            }

            return m.format(this.getDateTime().timeFormat);
        },

        convertTimeFromDisplay: function (value) {
            if (!value) {
                return null;
            }

            let m = moment(value, this.getDateTime().timeFormat);

            if (!m.isValid()) {
                return null;
            }

            return m.format('HH:mm');
        },

        afterRender: function () {
            this.$start = this.$el.find('[data-name="start"]');
            this.$end = this.$el.find('[data-name="end"]');

            this.initTimepicker(this.$start);
            this.initTimepicker(this.$end);

            this.setMinTime();

            this.$start.on('change', () => this.setMinTime());
        },

        setMinTime: function () {
            let value = this.$start.val();

            this.$end.timepicker('option', 'maxTime', this.convertTimeToDisplay('23:59'));

            if (!value) {
                this.$end.timepicker('option', 'minTime', null);

                return;
            }

            this.$end.timepicker('option', 'minTime', value);
        },

        initTimepicker: function ($el) {
            $el.timepicker({
                step: this.minuteStep,
                timeFormat: this.timeFormatMap[this.getDateTime().timeFormat],
            });

            $el.on('change', () => this.trigger('change'));

            $el.attr('autocomplete', 'espo-time-range-item');
        },

        fetch: function () {
            return [
                this.convertTimeFromDisplay(this.$start.val()),
                this.convertTimeFromDisplay(this.$end.val()),
            ];
        },
    });
});
PK]`��x#	#	=views/working-time-calendar/fields/time-ranges/item-detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/working-time-calendar/fields/time-ranges/item-detail', ['view', 'lib!moment'],
(Dep, /** @param {moment} */moment) => {

    /**
     * @extends module:view
     */
    class Class extends Dep
    {
        templateContent = `
            {{start}}
            &nbsp;–&nbsp;
            {{end}}
        `

        data() {
            return {
                start: this.convertTimeToDisplay(this.value[0]),
                end: this.convertTimeToDisplay(this.value[1]),
            };
        }

        setup() {
            this.value = this.options.value;
        }

        convertTimeToDisplay(value) {
            if (!value) {
                return '';
            }

            let m = moment(value, 'HH:mm');

            if (!m.isValid()) {
                return '';
            }

            return m.format(this.getDateTime().timeFormat);
        }
    }

    return Class;
});
PK]�N#Y(Y(1views/working-time-calendar/fields/time-ranges.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/working-time-calendar/fields/time-ranges', ['views/fields/base'], function (Dep) {

    /**
     * @class
     * @name Class
     * @memberOf module:views/working-time-calendar/fields/time-ranges
     * @extends module:views/fields/base
     */
    return Dep.extend(/** @lends module:views/working-time-calendar/fields/time-ranges.Class# */{

        listTemplateContent: `
            <div class="item-list">
            {{#each itemDataList}}
                <span class="item" data-key="{{key}}"
                >{{{var viewKey ../this}}}</span>{{#unless isLast}} &nbsp;&middot;&nbsp; {{/unless}}
            {{/each}}
            </div>
            {{#unless itemDataList.length}}
            <span class="none-value">{{translate 'None'}}</span>
            {{/unless}}
        `,

        detailTemplateContent: `
            <div class="item-list">
            {{#each itemDataList}}
                <div class="item" data-key="{{key}}">
                    {{{var viewKey ../this}}}
                </div>
            {{/each}}
            </div>
            {{#unless itemDataList.length}}
            <span class="none-value">{{translate 'None'}}</span>
            {{/unless}}
        `,

        editTemplateContent: `
            <div class="item-list">
            {{#each itemDataList}}
                <div class="item" data-key="{{key}}">
                    {{{var viewKey ../this}}}
                </div>
            {{/each}}
            </div>
            <div class="add-item-container margin-top-sm">
                <a
                    role="button"
                    tabindex="0"
                    class="add-item"
                    title="{{translate 'Add'}}"
                ><span class="fas fa-plus"></span></a>
            </div>
        `,

        fetchEmptyAsNull: false,

        validations: ['required', 'valid'],

        events: {
            'click .add-item': function () {
                this.addItem();
            },
            'click .remove-item': function (e) {
                let key = parseInt($(e.currentTarget).attr('data-key'));

                this.removeItem(key);
            },
        },

        data: function () {
            let data = Dep.prototype.data.call(this);

            data.itemDataList = this.itemKeyList.map((key, i) => {
                return {
                    key: key.toString(),
                    viewKey: this.composeViewKey(key),
                    isLast: i === this.itemKeyList.length - 1,
                };
            });

            return data;
        },

        prepare: function () {
            this.initItems();

            return this.createItemViews();
        },

        initItems: function () {
            this.itemKeyList = [];

            this.getItemListFromModel().forEach((item, i) => {
                this.itemKeyList.push(i);
            });
        },

        /**
         * @returns {Promise}
         */
        createItemView: function (item, key) {
            let viewName = this.isEditMode() ?
                'views/working-time-calendar/fields/time-ranges/item-edit' :
                'views/working-time-calendar/fields/time-ranges/item-detail';

            return this.createView(
                this.composeViewKey(key),
                viewName,
                {
                    value: item,
                    selector: '.item[data-key="' + key + '"]',
                    key: key,
                }
            )
            .then(view => {
                this.listenTo(view, 'change', () => {
                    this.trigger('change');
                });

                return view;
            });
        },

        /**
         * @returns {Promise}
         */
        createItemViews: function () {
            this.itemKeyList.forEach(key => {
                this.clearView(this.composeViewKey(key));
            });

            if (!this.model.has(this.name)) {
                return Promise.resolve();
            }

            let itemList = this.getItemListFromModel();

            let promiseList = [];

            this.itemKeyList.forEach((key, i) => {
                let item = itemList[i];

                let promise = this.createItemView(item, key);

                promiseList.push(promise);
            });

            return Promise.all(promiseList);
        },

        getItemView: function (key) {
            return this.getView(this.composeViewKey(key));
        },

        composeViewKey: function (key) {
            return 'item-' + key;
        },

        /**
         * @return {[string|null, string|null][]}
         */
        getItemListFromModel: function () {
            return Espo.Utils.cloneDeep(this.model.get(this.name) || []);
        },

        addItem: function () {
            let itemList = this.getItemListFromModel();

            let value = null;

            if (itemList.length) {
                value = itemList[itemList.length - 1][1];
            }

            let item = [value, null];

            itemList.push(item);

            let key = this.itemKeyList[this.itemKeyList.length - 1];

            if (typeof key === 'undefined') {
                key = 0;
            }

            key++;

            this.itemKeyList.push(key);

            this.$el.find('.item-list').append(
                $('<div>')
                    .addClass('item')
                    .attr('data-key', key)
            );

            this.createItemView(item, key)
                .then(view => view.render())
                .then(() => {
                    this.trigger('change');
                });
        },

        removeItem: function (key) {
            let index = this.itemKeyList.indexOf(key);

            if (key === -1) {
                return;
            }

            let itemList = this.getItemListFromModel();

            this.itemKeyList.splice(index, 1);
            itemList.splice(index, 1);

            this.model.set(this.name, itemList, {ui: true});

            this.clearView(this.composeViewKey(key));

            this.$el.find(`.item[data-key="${key}"`).remove();

            this.trigger('change');
        },

        fetch: function () {
            let itemList = [];

            this.itemKeyList.forEach(key => {
                itemList.push(
                    this.getItemView(key).fetch()
                );
            });

            let data = {};

            data[this.name] = Espo.Utils.cloneDeep(itemList);

            if (data[this.name].length === 0) {
                data[this.name] = null;
            }

            return data;
        },

        validateRequired: function () {
            if (!this.isRequired()) {
                return false;
            }

            if (this.getItemListFromModel().length) {
                return false;
            }

            let msg = this.translate('fieldIsRequired', 'messages')
                .replace('{field}', this.getLabelText());

            this.showValidationMessage(msg, '.add-item-container');

            return true;
        },

        validateValid: function () {
            if (!this.isRangesInvalid()) {
                return false;
            }

            let msg = this.translate('fieldInvalid', 'messages')
                .replace('{field}', this.getLabelText());

            this.showValidationMessage(msg, '.add-item-container');

            return true;
        },

        isRangesInvalid: function () {
            let itemList = this.getItemListFromModel();

            for (let i = 0; i < itemList.length; i++) {
                let item = itemList[i];

                if (this.isRangeInvalid(item[0], item[1], true)) {
                    return true;
                }

                if (i === 0) {
                    continue;
                }

                let prevItem = item[i - 1];

                if (this.isRangeInvalid(prevItem[1], item[0])) {
                    return true;
                }
            }

            return false;
        },

        /**
         * @param {string|null} from
         * @param {string|null} to
         * @param {boolean} [noEmpty]
         */
        isRangeInvalid: function (from, to, noEmpty) {
            if (from === null || to === null) {
                return true;
            }

            let fromNumber = parseFloat(from.replace(':', '.'));
            let toNumber = parseFloat(to.replace(':', '.'));

            if (noEmpty && fromNumber === toNumber) {
                return true;
            }

            return fromNumber > toNumber;
        },
    });
});
PK]I�e���views/collapsed-modal-bar.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';

class CollapsedModalBar extends View {

    maxNumberToDisplay = 3

    // language=Handlebars
    templateContent = `
        {{#each dataList}}
        <div class="collapsed-modal" data-number="{{number}}">{{var key ../this}}</div>
        {{/each}}
    `

    data() {
        return {
            dataList: this.getDataList(),
        };
    }

    init() {
        this.on('render', () => {
            if ($('.collapsed-modal-bar').length === 0) {
                $('<div />')
                    .addClass('collapsed-modal-bar')
                    .appendTo('body');
            }
        });
    }

    setup() {
        this.lastNumber = 0;
        this.numberList = [];
    }

    getDataList() {
        let list = [];

        let numberList = Espo.Utils.clone(this.numberList);

        if (this.numberList.length > this.maxNumberToDisplay) {
            numberList = numberList.slice(this.numberList.length - this.maxNumberToDisplay);
        }

        numberList
            .reverse()
            .forEach((number, i) => {
                list.push({
                    number: number.toString(),
                    key: 'key-' + number,
                    index: i,
                });
            });

        return list;
    }

    calculateDuplicateNumber(title) {
        let duplicateNumber = 0;

        this.numberList.forEach(number => {
            let view = this.getModalViewByNumber(number);

            if (!view) {
                return;
            }

            if (view.title === title) {
                duplicateNumber++;
            }
        });

        if (duplicateNumber === 0) {
            return null;
        }

        return duplicateNumber;
    }

    getModalViewByNumber(number) {
        let key = 'key-' + number;

        return this.getView(key);
    }

    addModalView(modalView, options) {
        let number = this.lastNumber;

        this.numberList.push(this.lastNumber);

        let key = 'key-' + number;

        this.createView(key, 'views/collapsed-modal', {
            title: options.title,
            duplicateNumber: this.calculateDuplicateNumber(options.title),
            selector: '[data-number="' + number + '"]',
        })
        .then(view => {
            this.listenToOnce(view, 'close', () => {
                this.removeModalView(number);
            });

            this.listenToOnce(view, 'expand', () => {
                this.removeModalView(number, true);

                // Use timeout to prevent DOM being updated after modal is re-rendered.
                setTimeout(() => {
                    let key = 'dialog-' + number;

                    this.setView(key, modalView);

                    modalView.setSelector(modalView.containerSelector);

                    this.getView(key).render();
                }, 5);
            });

            this.reRender(true);
        });

        this.lastNumber++;
    }

    removeModalView(number, noReRender) {
        let key = 'key-' + number;

        let index = this.numberList.indexOf(number);

        if (~index) {
            this.numberList.splice(index, 1);
        }

        if (this.isRendered()) {
            this.$el.find('.collapsed-modal[data-number="' + number + '"]').remove();
        }

        if (!noReRender) {
            this.reRender();
        }

        this.clearView(key);
    }
}

// noinspection JSUnusedGlobalSymbols
export default CollapsedModalBar;
PK]��QQ views/portal-role/record/edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/portal-role/record/edit', ['views/role/record/edit'], function (Dep) {

    return Dep.extend({

        tableView: 'views/portal-role/record/table',

        stickButtonsContainerAllTheWay: true,
    });
});
PK]b�v:c
c
!views/portal-role/record/table.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/portal-role/record/table', ['views/role/record/table'], function (Dep) {

    return Dep.extend({

        levelListMap: {
            'recordAllAccountContactOwnNo': ['all', 'account', 'contact', 'own', 'no'],
            'recordAllAccountOwnNo': ['all', 'account', 'own', 'no'],
            'recordAllContactOwnNo': ['all', 'contact', 'own', 'no'],
            'recordAllAccountNo': ['all', 'account', 'no'],
            'recordAllContactNo': ['all', 'contact', 'no'],
            'recordAllAccountContactNo': ['all', 'account', 'contact', 'no'],
            'recordAllOwnNo': ['all', 'own', 'no'],
            'recordAllNo': ['all', 'no'],
            'record': ['all', 'own', 'no']
        },

        levelList: [
            'all',
            'account',
            'contact',
            'own',
            'no',
        ],

        type: 'aclPortal',

        lowestLevelByDefault: true,

        setupScopeList: function () {
            this.aclTypeMap = {};
            this.scopeList = [];

            var scopeListAll = Object.keys(this.getMetadata().get('scopes'))
                .sort((v1, v2) => {
                     return this.translate(v1, 'scopeNamesPlural')
                         .localeCompare(this.translate(v2, 'scopeNamesPlural'));
                });

            scopeListAll.forEach(scope => {
                if (
                    this.getMetadata().get('scopes.' + scope + '.disabled') ||
                    this.getMetadata().get('scopes.' + scope + '.disabledPortal')
                ) {
                    return;
                }

                var acl = this.getMetadata().get('scopes.' + scope + '.aclPortal');

                if (acl) {
                    this.scopeList.push(scope);
                    this.aclTypeMap[scope] = acl;

                    if (acl === true) {
                        this.aclTypeMap[scope] = 'record';
                    }
                }
            });
        },
    });
});
PK]	_lUU"views/portal-role/record/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/portal-role/record/detail', ['views/role/record/detail'], function (Dep) {

    return Dep.extend({

        tableView: 'views/portal-role/record/table',

        stickButtonsContainerAllTheWay: true,
    });
});
PK]���� views/portal-role/record/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/portal-role/record/list', ['views/role/record/list'], function (Dep) {

    return Dep.extend({});
});
PK]j
�M��views/portal-role/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/portal-role/list', ['views/list'], function (Dep) {

    return Dep.extend({

        searchPanel: false,
    });
});
PK]���.	.	views/modals/view-map.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ModalView from 'views/modal';

class ViewMapModalView extends ModalView {

    templateContent = `<div class="map-container no-side-margin">{{{map}}}</div>`

    backdrop = true

    setup() {
        let field = this.options.field;

        let url = '#AddressMap/view/' + this.model.entityType + '/' + this.model.id + '/' + field;
        let fieldLabel = this.translate(field, 'fields', this.model.entityType);

        this.headerElement =
            $('<a>')
                .attr('href', '#' + url)
                .text(fieldLabel)
                .get(0);

        let viewName = this.model.getFieldParam(field + 'Map', 'view') ||
            this.getFieldManager().getViewName('map');

        this.createView('map', viewName, {
            model: this.model,
            name: field + 'Map',
            selector: '.map-container',
            height: 'auto',
        });
    }
}

export default ViewMapModalView;
PK]��X�<<,views/modals/select-category-tree-records.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import SelectRecordsModalView from 'views/modals/select-records';

class SelectCategoryTreeRecordsModalView extends SelectRecordsModalView {

    setup() {
        this.filters = this.options.filters || {};
        this.boolFilterList = this.options.boolFilterList || {};
        this.primaryFilterName = this.options.primaryFilterName || null;

        if ('multiple' in this.options) {
            this.multiple = this.options.multiple;
        }

        this.createButton = false;
        this.massRelateEnabled = this.options.massRelateEnabled;

        this.buttonList = [
            {
                name: 'cancel',
                label: 'Cancel'
            }
        ];

        if (this.multiple) {
            this.buttonList.unshift({
                name: 'select',
                style: 'danger',
                label: 'Select',
                onClick: dialog => {
                    let listView = this.getRecordView();

                    if (listView.allResultIsChecked) {
                        this.trigger('select', {
                            massRelate: true,
                            where: this.collection.getWhere(),
                            searchParams: this.collection.data,
                        });
                    }
                    else {
                        var list = listView.getSelected();
                        if (list.length) {
                            this.trigger('select', list);
                        }
                    }

                    dialog.close();
                },
            });
        }

        this.scope = this.options.scope;

        this.$header = $('<span>');

        this.$header.append(
            $('<span>').text(
                this.translate('Select') + ': ' +
                this.getLanguage().translate(this.scope, 'scopeNamesPlural')
            )
        );

        this.$header.prepend(
            this.getHelper().getScopeColorIconHtml(this.scope)
        );

        this.waitForView('list');

        Espo.loader.require('search-manager', SearchManager => {
            this.getCollectionFactory().create(this.scope, collection => {
                collection.maxSize = this.getConfig().get('recordsPerPageSelect') || 5;

                this.collection = collection;

                var searchManager = new SearchManager(collection, 'listSelect', null, this.getDateTime());

                searchManager.emptyOnReset = true;

                if (this.filters) {
                    searchManager.setAdvanced(this.filters);
                }

                if (this.boolFilterList) {
                    searchManager.setBool(this.boolFilterList);
                }

                if (this.primaryFilterName) {
                    searchManager.setPrimary(this.primaryFilterName);
                }

                collection.where = searchManager.getWhere();
                collection.url = collection.entityType + '/action/listTree';

                var viewName = this.getMetadata()
                    .get('clientDefs.' + this.scope + '.recordViews.listSelectCategoryTree') ||
                    'views/record/list-tree';

                this.listenToOnce(collection, 'sync', () => {
                    this.createView('list', viewName, {
                        collection: collection,
                        fullSelector: this.containerSelector + ' .list-container',
                        readOnly: true,
                        selectable: true,
                        checkboxes: this.multiple,
                        massActionsDisabled: true,
                        searchManager: searchManager,
                        checkAllResultDisabled: true,
                        buttonsDisabled: true,
                    }, listView => {
                        listView.once('select', model => {
                            this.trigger('select', model);
                            this.close();
                        });
                    });
                });

                collection.fetch();
            });
        });
    }
}

// noinspection JSUnusedGlobalSymbols
export default SelectCategoryTreeRecordsModalView;
PK]��H��views/modals/add-dashlet.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ModalView from 'views/modal';

class AddDashletModalView extends ModalView {

    template = 'modals/add-dashlet'

    cssName = 'add-dashlet'
    backdrop = true

    events = {
        /** @this AddDashletModalView */
        'click .add': function (e) {
            var name = $(e.currentTarget).data('name');
            this.trigger('add', name);
            this.close();
        },
        /** @this AddDashletModalView */
        'keyup input[data-name="quick-search"]': function (e) {
            this.processQuickSearch(e.currentTarget.value);
        },
    }

    data() {
        return {
            dashletList: this.dashletList,
        };
    }

    setup() {
        this.headerText = this.translate('Add Dashlet');

        let dashletList = Object.keys(this.getMetadata().get('dashlets') || {})
            .sort((v1, v2) => {
                return this.translate(v1, 'dashlets').localeCompare(this.translate(v2, 'dashlets'));
            });

        this.translations = {};

        this.dashletList = dashletList.filter(item => {
            let aclScope = this.getMetadata().get(['dashlets', item, 'aclScope']) || null;
            let accessDataList = this.getMetadata().get(['dashlets', item, 'accessDataList']) || null;

            if (this.options.parentType === 'Settings') {
                return true;
            }

            if (this.options.parentType === 'Portal') {
                if (accessDataList && accessDataList.find(item => item.inPortalDisabled)) {
                    return false;
                }

                return true;
            }

            if (aclScope) {
                if (!this.getAcl().check(aclScope)) {
                    return false;
                }
            }

            if (accessDataList) {
                if (!Espo.Utils.checkAccessDataList(accessDataList, this.getAcl(), this.getUser())) {
                    return false;
                }
            }

            this.translations[item] = this.translate(item, 'dashlets');

            return true;
        });
    }

    afterRender() {
        this.$noData = this.$el.find('.no-data');

        setTimeout(() => {
            this.$el.find('input[data-name="quick-search"]').focus()
        }, 100);
    }

    processQuickSearch(text) {
        text = text.trim();

        let $noData = this.$noData;

        $noData.addClass('hidden');

        if (!text) {
            this.$el.find('ul .list-group-item').removeClass('hidden');

            return;
        }

        let matchedList = [];

        let lowerCaseText = text.toLowerCase();

        this.dashletList.forEach(item => {
            let label = this.translations[item].toLowerCase();

            for (let word of label.split(' ')) {
                let matched = word.indexOf(lowerCaseText) === 0;

                if (matched) {
                    matchedList.push(item);

                    return;
                }
            }
        });

        if (matchedList.length === 0) {
            this.$el.find('ul .list-group-item').addClass('hidden');

            $noData.removeClass('hidden');

            return;
        }

        this.dashletList.forEach(item => {
            let $row = this.$el.find(`ul .list-group-item[data-name="${item}"]`);

            if (!~matchedList.indexOf(item)) {
                $row.addClass('hidden');

                return;
            }

            $row.removeClass('hidden');
        });
    }
}

export default AddDashletModalView;
PK]E��0%0%views/modals/compose-email.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import EditModalView from 'views/modals/edit';

class ComposeEmailModalView extends EditModalView {

    scope = 'Email'
    layoutName = 'composeSmall'
    saveDisabled = true
    fullFormDisabled = true
    isCollapsable = true
    wasModified = false

    shortcutKeys = {
        /** @this ComposeEmailModalView */
        'Control+Enter': function (e) {
            if (this.buttonList.findIndex(item => item.name === 'send' && !item.hidden) === -1) {
                return;
            }

            e.stopPropagation();
            e.preventDefault();

            this.actionSend();
        },
        /** @this ComposeEmailModalView */
        'Control+KeyS': function (e) {
            if (this.buttonList.findIndex(item => item.name === 'saveDraft' && !item.hidden) === -1) {
                return;
            }

            e.preventDefault();
            e.stopPropagation();

            this.actionSaveDraft();
        },
        /** @this ComposeEmailModalView */
        'Escape': function (e) {
            e.stopPropagation();
            e.preventDefault();

            let focusedFieldView = this.getRecordView().getFocusedFieldView();

            if (focusedFieldView) {
                this.model.set(focusedFieldView.fetch());
            }

            if (this.getRecordView().isChanged) {
                this.confirm(this.translate('confirmLeaveOutMessage', 'messages'))
                    .then(() => this.actionClose());

                return;
            }

            this.actionClose();
        },
    }

    setup() {
        super.setup();

        this.buttonList.unshift({
            name: 'saveDraft',
            text: this.translate('Save Draft', 'labels', 'Email'),
            title: 'Ctrl+S',
        });

        this.buttonList.unshift({
            name: 'send',
            text: this.translate('Send', 'labels', 'Email'),
            style: 'primary',
            title: 'Ctrl+Enter',
        });

        this.$header = $('<a>')
            .attr('role', 'button')
            .attr('tabindex', '0')
            .attr('data-action', 'fullFormDraft')
            .text(this.getLanguage().translate('Compose Email'));

        this.events['click a[data-action="fullFormDraft"]'] = () => this.actionFullFormDraft();

        if (
            this.getConfig().get('emailForceUseExternalClient') ||
            this.getPreferences().get('emailUseExternalClient') ||
            !this.getAcl().checkScope('Email', 'create')
        ) {
            var attributes = this.options.attributes || {};

            Espo.loader.require('email-helper', EmailHelper => {
                this.getRouter().confirmLeaveOut = false;

                let emailHelper = new EmailHelper();

                document.location.href = emailHelper
                    .composeMailToLink(attributes, this.getConfig().get('outboundEmailBccAddress'));
            });

            this.once('after:render', () => {
                this.actionClose();
            });

            return;
        }

        this.once('remove', () => {
            this.dialogIsHidden = false;
        });

        this.listenTo(this.model, 'change', (m, o) => {
            if (o.ui) {
                this.wasModified = true;
            }
        });
    }

    createRecordView(model, callback) {
        let viewName = this.getMetadata().get('clientDefs.' + model.entityType + '.recordViews.compose') ||
            'views/email/record/compose';

        let options = {
            model: model,
            fullSelector: this.containerSelector + ' .edit-container',
            type: 'editSmall',
            layoutName: this.layoutName || 'detailSmall',
            buttonsDisabled: true,
            selectTemplateDisabled: this.options.selectTemplateDisabled,
            removeAttachmentsOnSelectTemplate: this.options.removeAttachmentsOnSelectTemplate,
            signatureDisabled: this.options.signatureDisabled,
            appendSignature: this.options.appendSignature,
            focusForCreate: this.options.focusForCreate,
            exit: () => {},
        };

        this.createView('edit', viewName, options, callback);
    }

    actionSend() {
        let dialog = this.dialog;

        /** @type {module:views/email/record/edit} editView */
        let editView = this.getRecordView();

        let model = editView.model;

        let afterSend = () => {
            this.dialogIsHidden = false;

            this.trigger('after:save', model);
            this.trigger('after:send', model);

            dialog.close();

            this.stopListening(editView, 'before:save', beforeSave);
            this.stopListening(editView, 'error:save', errorSave);

            this.remove();
        };

        let beforeSave = () => {
            this.dialogIsHidden = true;

            dialog.hideWithBackdrop();

            editView.setConfirmLeaveOut(false);

            if (!this.forceRemoveIsInitiated) {
                this.initiateForceRemove();
            }
        };

        let errorSave = () => {
            this.dialogIsHidden = false;

            if (this.isRendered()) {
                dialog.show();
            }
        };

        this.listenToOnce(editView, 'after:send', afterSend);

        this.disableButton('send');
        this.disableButton('saveDraft');

        this.listenToOnce(editView, 'cancel:save', () => {
            this.enableButton('send');
            this.enableButton('saveDraft');

            this.stopListening(editView, 'after:send', afterSend);

            this.stopListening(editView, 'before:save', beforeSave);
            this.stopListening(editView, 'error:save', errorSave);
        });

        this.listenToOnce(editView, 'before:save', beforeSave);
        this.listenToOnce(editView, 'error:save', errorSave);

        editView.send();
    }

    actionSaveDraft(options) {
        /** @type {module:views/email/record/edit} editView */
        let editView = this.getRecordView();

        let model = editView.model;

        this.disableButton('send');
        this.disableButton('saveDraft');

        let afterSave = () => {
            this.enableButton('send');
            this.enableButton('saveDraft');

            Espo.Ui.success(this.translate('savedAsDraft', 'messages', 'Email'))

            this.trigger('after:save', model);

            this.$el.find('button[data-name="cancel"]').html(this.translate('Close'));
        };

        editView.once('after:save', () => afterSave());

        editView.once('cancel:save', () => {
            this.enableButton('send');
            this.enableButton('saveDraft');

            editView.off('after:save', afterSave);
        });

        return editView.saveDraft(options);
    }

    initiateForceRemove() {
        this.forceRemoveIsInitiated = true;

        let parentView = this.getParentView();

        if (!parentView) {
            return true;
        }

        parentView.once('remove', () => {
            if (!this.dialogIsHidden) {
                return;
            }

            this.remove();
        });
    }

    actionFullFormDraft() {
        this.actionSaveDraft()
            .then(() => {
                this.getRouter().navigate('#Email/edit/' + this.model.id, {trigger: true});

                this.close();
            })
            .catch(reason => {
                if (reason === 'notModified') {
                    Espo.Ui.notify(false);

                    this.getRouter().navigate('#Email/edit/' + this.model.id, {trigger: true});
                }
            });
    }

    beforeCollapse() {
        if (this.wasModified) {
            this.actionSaveDraft({skipNotModifiedWarning: true});
        }

        this.getRecordView().setConfirmLeaveOut(false);

        return super.beforeCollapse();
    }
}

export default ComposeEmailModalView;
PK]>�011views/modals/save-filters.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ModalView from 'views/modal';
import Model from 'model';

class SaveFiltersModalView extends ModalView {

    template = 'modals/save-filters'

    cssName = 'save-filters'

    data() {
        return {
            dashletList: this.dashletList,
        };
    }

    setup() {
        this.buttonList = [
            {
                name: 'save',
                label: 'Save',
                style: 'primary',
            },
            {
                name: 'cancel',
                label: 'Cancel',
            },
        ];

        this.headerText = this.translate('Save Filter');

        let model = new Model();

        this.createView('name', 'views/fields/varchar', {
            selector: '.field[data-name="name"]',
            defs: {
                name: 'name',
                params: {
                    required: true
                }
            },
            mode: 'edit',
            model: model,
        });
    }

    /**
     * @param {string} field
     * @return {module:views/fields/base}
     */
    getFieldView(field) {
        return this.getView(field);
    }

    actionSave() {
        let nameView = this.getFieldView('name');

        nameView.fetchToModel();

        if (nameView.validate()) {
            return;
        }

        this.trigger('save', nameView.model.get('name'));

        return true;
    }
}

export default SaveFiltersModalView;
PK]ȯ_�,�,views/modals/mass-update.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ModalView from 'views/modal';
import MassActionHelper from 'helpers/mass-action';
import Select from 'ui/select';

class MassUpdateModalView extends ModalView {

    template = 'modals/mass-update'

    cssName = 'mass-update'
    className = 'dialog dialog-record'
    layoutName = 'massUpdate'

    ACTION_UPDATE = 'update'
    //ACTION_ADD = 'add'
    //ACTION_REMOVE = 'remove'

    data() {
        return {
            scope: this.scope,
            fieldList: this.fieldList,
            entityType: this.entityType,
        };
    }

    events = {
        /** @this MassUpdateModalView */
        'click a[data-action="add-field"]': function (e) {
            const field = $(e.currentTarget).data('name');

            this.addField(field);
        },
        /** @this MassUpdateModalView */
        'click button[data-action="reset"]': function () {
            this.reset();
        }
    }

    setup() {
        this.buttonList = [
            {
                name: 'update',
                label: 'Update',
                style: 'danger',
                disabled: true,
            },
            {
                name: 'cancel',
                label: 'Cancel',
            }
        ];

        this.entityType = this.options.entityType || this.options.scope;
        this.scope = this.options.scope || this.entityType;

        this.ids = this.options.ids;
        this.where = this.options.where;
        this.searchParams = this.options.searchParams;
        this.byWhere = this.options.byWhere;

        this.hasActionMap = {};

        const totalCount = this.options.totalCount;

        this.helper = new MassActionHelper(this);

        this.idle = this.byWhere && this.helper.checkIsIdle(totalCount);

        this.$header = $('<span>')
            .append(
                $('<span>').text(this.translate(this.scope, 'scopeNamesPlural')),
                ' <span class="chevron-right"></span> ',
                $('<span>').text(this.translate('Mass Update'))
            )

        var forbiddenList = this.getAcl().getScopeForbiddenFieldList(this.entityType, 'edit') || [];

        this.wait(true);

        this.getModelFactory().create(this.entityType, (model) => {
            this.model = model;

            this.getHelper().layoutManager.get(this.entityType, this.layoutName, (layout) => {
                layout = layout || [];

                this.fieldList = [];

                layout.forEach((field) => {
                    if (~forbiddenList.indexOf(field)) {
                        return;
                    }

                    if (model.hasField(field)) {
                        this.fieldList.push(field);
                    }
                });

                this.wait(false);
            });
        });

        this.addedFieldList = [];
    }

    addField(name) {
        this.$el.find('[data-action="reset"]').removeClass('hidden');

        this.$el.find('ul.filter-list li[data-name="'+name+'"]').addClass('hidden');

        if (this.$el.find('ul.filter-list li:not(.hidden)').length === 0) {
            this.$el.find('button.select-field').addClass('disabled').attr('disabled', 'disabled');
        }

        this.addedFieldList.push(name);

        const label = this.getHelper().escapeString(
            this.translate(name, 'fields', this.entityType)
        );

        const $cell =
            $('<div>')
                .addClass('cell form-group')
                .attr('data-name', name)
                .append(
                    $('<label>')
                        .addClass('control-label')
                        .text(label)
                )
                .append(
                    $('<div>')
                        .addClass('field')
                        .attr('data-name', name)
                );

        const $row =
            $('<div>')
                .addClass('item grid-auto-fill-md')
                .attr('data-name', name)
                .append($cell);

        this.$el.find('.fields-container').append($row);

        const type = this.model.getFieldType(name);
        const viewName = this.model.getFieldParam(name, 'view') || this.getFieldManager().getViewName(type);

        const actionList = this.getMetadata().get(['entityDefs', this.entityType, name, 'massUpdateActionList']) ||
            this.getMetadata().get(['fields', type, 'massUpdateActionList']);

        const hasActionDropdown = actionList !== null;

        this.hasActionMap[name] = hasActionDropdown;

        this.disableButton('update');

        this.createView(name, viewName, {
            model: this.model,
            selector: '.field[data-name="' + name + '"]',
            defs: {
                name: name,
            },
            mode: 'edit',
        }, view => {
            this.enableButton('update');

            view.render();
        });

        if (hasActionDropdown) {
            const $select =
                $('<select>')
                    .addClass('item-action form-control')
                    .attr('data-name', name);

            actionList.forEach(action => {
                const label = this.translate(Espo.Utils.upperCaseFirst(action));

                $select.append(
                    $('<option>')
                        .text(label)
                        .val(action)
                );
            });

            const $cellAction =
                $('<div>')
                    .addClass('cell call-action form-group')
                    .attr('data-name', name)
                    .append(
                        $('<label>')
                            .addClass('control-label hidden-xs')
                            .html('&nbsp;')
                    )
                    .append(
                        $('<div>')
                            .addClass('field')
                            .attr('data-name', name)
                            .append($select)
                    );

            $row.append($cellAction);

            Select.init($select.get(0));
        }
    }

    /**
     * @param {string} field
     * @return {module:views/fields/base}
     */
    getFieldView(field) {
        return this.getView(field);
    }

    // noinspection JSUnusedGlobalSymbols
    actionUpdate() {
        this.disableButton('update');

        const attributes = {};
        const actions = {};

        this.addedFieldList.forEach(field => {
            const action = this.fetchAction(field);
            const itemAttributes = this.getFieldView(field).fetch();

            const itemActualAttributes = {};

            this.getFieldManager()
                .getEntityTypeFieldActualAttributeList(this.entityType, field)
                .forEach(attribute => {
                    actions[attribute] = action;

                    itemActualAttributes[attribute] = itemAttributes[attribute];
                });

            _.extend(attributes, itemActualAttributes);
        });

        this.model.set(attributes);

        let notValid = false;

        this.addedFieldList.forEach(field => {
            const view = this.getFieldView(field);

            notValid = view.validate() || notValid;
        });

        if (notValid) {
            Espo.Ui.error(this.translate('Not valid'))

            this.enableButton('update');

            return;
        }

        Espo.Ui.notify(this.translate('saving', 'messages'));

        Espo.Ajax
            .postRequest('MassAction', {
                action: 'update',
                entityType: this.entityType,
                params: {
                    ids: this.ids || null,
                    where: (!this.ids || this.ids.length === 0) ? this.options.where : null,
                    searchParams: (!this.ids || this.ids.length === 0) ? this.options.searchParams : null,
                },
                data: {
                    values: attributes,
                    actions: actions,
                },
                idle: this.idle,
            })
            .then(result => {
                result = result || {};

                if (result.id) {
                    this.helper
                        .process(result.id, 'update')
                        .then(view => {
                            this.listenToOnce(view, 'close', () => this.close());

                            this.listenToOnce(view, 'success', result => {
                                this.trigger('after:update', {
                                    count: result.count,
                                    idle: true,
                                });
                            });
                        });

                    return;
                }

                this.trigger('after:update', {
                    count: result.count,
                });
            })
            .catch(() => {
                this.enableButton('update');
            });
    }

    fetchAction(name) {
        if (!this.hasActionMap[name]) {
            return this.ACTION_UPDATE;
        }

        const $dropdown = this.$el.find('select.item-action[data-name="' + name + '"]');

        return $dropdown.val() || this.ACTION_UPDATE;
    }

    reset() {
        this.addedFieldList.forEach(field => {
            this.clearView(field);

            this.$el.find('.item[data-name="'+field+'"]').remove();
        });

        this.addedFieldList = [];
        this.hasActionMap = {};

        this.model.clear();

        this.$el.find('[data-action="reset"]').addClass('hidden');
        this.$el.find('button.select-field').removeClass('disabled').removeAttr('disabled');
        this.$el.find('ul.filter-list').find('li').removeClass('hidden');

        this.disableButton('update');
    }
}

export default MassUpdateModalView;
PK]��':s
s
views/modals/last-viewed.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ModalView from 'views/modal';

class LastViewedModalView extends ModalView {

    scope = 'ActionHistoryRecord'
    className = 'dialog dialog-record'
    template = 'modals/last-viewed'
    backdrop = true

    setup() {
        this.events['click .list .cell > a'] = () => {
            this.close();
        };

        this.$header = $('<a>')
            .attr('href', '#LastViewed')
            .attr('data-action', 'listView')
            .addClass('action')
            .text(this.getLanguage().translate('LastViewed', 'scopeNamesPlural'));

        this.waitForView('list');

        this.getCollectionFactory().create(this.scope, collection => {
            collection.maxSize = this.getConfig().get('recordsPerPage');
            collection.url = 'LastViewed';

            this.collection = collection;

            this.loadList();

            collection.fetch();
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionListView() {
        this.getRouter().navigate('#LastViewed', {trigger: true});

        this.close();
    }

    loadList() {
        let viewName =
            this.getMetadata().get('clientDefs.' + this.scope + '.recordViews.listLastViewed') ||
            'views/record/list';

        this.listenToOnce(this.collection, 'sync', () => {
            this.createView('list', viewName, {
                collection: this.collection,
                fullSelector: this.containerSelector + ' .list-container',
                selectable: false,
                checkboxes: false,
                massActionsDisabled: true,
                rowActionsView: false,
                searchManager: this.searchManager,
                checkAllResultDisabled: true,
                buttonsDisabled: true,
                headerDisabled: true,
                layoutName: 'listForLastViewed',
                layoutAclDisabled: true,
            });
        });
    }
}

// noinspection JSUnusedGlobalSymbols
export default LastViewedModalView;
PK]�2"Kviews/modals/action-history.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ModalView from 'views/modal';
import SearchManager from 'search-manager';

class ActionHistoryModalView extends ModalView {

    template = 'modals/action-history'

    scope = 'ActionHistoryRecord'
    className = 'dialog dialog-record'
    backdrop = true

    setup() {
        super.setup();

        this.buttonList = [
            {
                name: 'cancel',
                label: 'Close',
            },
        ];

        this.scope = this.entityType = this.options.scope || this.scope;

        this.$header = $('<a>')
            .attr('href', '#ActionHistoryRecord')
            .addClass('action')
            .attr('data-action', 'listView')
            .text(this.getLanguage().translate(this.scope, 'scopeNamesPlural'));

        this.waitForView('list');

        this.getCollectionFactory().create(this.scope, collection => {
            collection.maxSize = this.getConfig().get('recordsPerPage') || 20;
            this.collection = collection;

            this.setupSearch();
            this.setupList();

            collection.fetch();
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionListView() {
        this.getRouter().navigate('#ActionHistoryRecord', {trigger: true});
        this.close();
    }

    setupSearch() {
        let searchManager = this.searchManager =
            new SearchManager(this.collection, 'listSelect', null, this.getDateTime());

        this.collection.data.boolFilterList = ['onlyMy'];
        this.collection.where = searchManager.getWhere();

        this.createView('search', 'views/record/search', {
            collection: this.collection,
            fullSelector: this.containerSelector + ' .search-container',
            searchManager: searchManager,
            disableSavePreset: true,
            textFilterDisabled: true,
        });
    }

    setupList() {
        let viewName = this.getMetadata().get(`clientDefs.${this.scope}.recordViews.list`) ||
           'views/record/list';

        this.listenToOnce(this.collection, 'sync', () => {
            this.createView('list', viewName, {
                collection: this.collection,
                fullSelector: this.containerSelector + ' .list-container',
                selectable: false,
                checkboxes: false,
                massActionsDisabled: true,
                rowActionsView: 'views/record/row-actions/view-only',
                type: 'listSmall',
                searchManager: this.searchManager,
                checkAllResultDisabled: true,
                buttonsDisabled: true,
            });
        });
    }
}

export default ActionHistoryModalView;
PK]Y�{A�<�<views/modals/select-records.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/modals/select-records */

import ModalView from 'views/modal';
import SearchManager from 'search-manager';

/**
 * A select-records modal.
 */
class SelectRecordsModalView extends ModalView {

    template = 'modals/select-records'

    cssName = 'select-modal'
    className = 'dialog dialog-record'
    multiple = false
    createButton = true
    searchPanel = true
    scope = ''
    noCreateScopeList = ['User', 'Team', 'Role', 'Portal']
    layoutName = 'listSmall'

    /** @inheritDoc */
    shortcutKeys = {
        /** @this SelectRecordsModalView */
        'Control+Enter': function (e) {
            this.handleShortcutKeyCtrlEnter(e);
        },
        /** @this SelectRecordsModalView */
        'Control+Space': function (e) {
            this.handleShortcutKeyCtrlSpace(e);
        },
        /** @this SelectRecordsModalView */
        'Control+Slash': function (e) {
            this.handleShortcutKeyCtrlSlash(e);
        },
        /** @this SelectRecordsModalView */
        'Control+Comma': function (e) {
            this.handleShortcutKeyCtrlComma(e);
        },
        /** @this SelectRecordsModalView */
        'Control+Period': function (e) {
            this.handleShortcutKeyCtrlPeriod(e);
        },
    }

    events = {
        /** @this SelectRecordsModalView */
        'click button[data-action="create"]': function () {
            this.create();
        },
        /** @this SelectRecordsModalView */
        'click .list a': function (e) {
            e.preventDefault();
        },
    }

    data() {
        return {
            createButton: this.createButton,
            createText: this.translate('Create ' + this.scope, 'labels', this.scope),
        };
    }

    setup() {
        this.filters = this.options.filters || {};
        this.boolFilterList = this.options.boolFilterList;
        this.primaryFilterName = this.options.primaryFilterName || null;
        this.filterList = this.options.filterList || this.filterList || null;
        this.layoutName = this.options.layoutName || this.layoutName;

        if ('multiple' in this.options) {
            this.multiple = this.options.multiple;
        }

        if ('createButton' in this.options) {
            this.createButton = this.options.createButton;
        }

        this.massRelateEnabled = this.options.massRelateEnabled;

        this.buttonList = [
            {
                name: 'cancel',
                label: 'Cancel',
            },
        ];

        if (this.multiple) {
            this.buttonList.unshift({
                name: 'select',
                style: 'danger',
                label: 'Select',
                disabled: true,
                title: 'Ctrl+Enter',
            });
        }

        this.scope = this.entityType = this.options.scope || this.scope;

        const customDefaultOrderBy = this.getMetadata().get(['clientDefs', this.scope, 'selectRecords', 'orderBy']);
        const customDefaultOrder = this.getMetadata().get(['clientDefs', this.scope, 'selectRecords', 'order']);

        if (customDefaultOrderBy) {
            this.defaultOrderBy = customDefaultOrderBy;
            this.defaultOrder = customDefaultOrder || false;
        }

        if (this.noCreateScopeList.indexOf(this.scope) !== -1) {
            this.createButton = false;
        }

        if (this.createButton) {
            if (
                !this.getAcl().check(this.scope, 'create') ||
                this.getMetadata().get(['clientDefs', this.scope, 'createDisabled'])
            ) {
                this.createButton = false;
            }
        }

        if (this.createButton) {
            this.addButton({
                name: 'create',
                position: 'right',
                onClick: () => this.create(),
                label: 'Create',
            });
        }

        if (this.getMetadata().get(['clientDefs', this.scope, 'searchPanelDisabled'])) {
            this.searchPanel = false;
        }

        if (this.getUser().isPortal()) {
            if (this.getMetadata().get(['clientDefs', this.scope, 'searchPanelInPortalDisabled'])) {
                this.searchPanel = false;
            }
        }

        this.$header = $('<span>');

        this.$header.append(
            $('<span>').text(
                this.translate('Select') + ' · ' +
                this.getLanguage().translate(this.scope, 'scopeNamesPlural')
            )
        );

        this.$header.prepend(
            this.getHelper().getScopeColorIconHtml(this.scope)
        );

        this.waitForView('list');

        if (this.searchPanel) {
            this.waitForView('search');
        }

        this.getCollectionFactory().create(this.scope, (collection) => {
            collection.maxSize = this.getConfig().get('recordsPerPageSelect') || 5;

            this.collection = collection;

            if (this.defaultOrderBy) {
                this.collection.setOrder(this.defaultOrderBy, this.defaultOrder || 'asc', true);
            }

            this.setupSearch();
            this.setupList();
        });

        // If the list not yet loaded.
        this.once('close', () => {
            if (
                this.collection.lastSyncPromise &&
                this.collection.lastSyncPromise.getStatus() < 4
            ) {
                Espo.Ui.notify(false);
            }

            this.collection.abortLastFetch();
        });
    }

    setupSearch() {
        const searchManager = this.searchManager =
            new SearchManager(this.collection, 'listSelect', null, this.getDateTime());

        searchManager.emptyOnReset = true;

        if (this.filters) {
            searchManager.setAdvanced(this.filters);
        }

        const boolFilterList = this.boolFilterList ||
            this.getMetadata().get('clientDefs.' + this.scope + '.selectDefaultFilters.boolFilterList');

        if (boolFilterList) {
            const d = {};

            boolFilterList.forEach(item => {
                d[item] = true;
            });

            searchManager.setBool(d);
        }

        const primaryFilterName = this.primaryFilterName ||
            this.getMetadata().get('clientDefs.' + this.scope + '.selectDefaultFilters.filter');

        if (primaryFilterName) {
            searchManager.setPrimary(primaryFilterName);
        }

        this.collection.where = searchManager.getWhere();

        if (this.searchPanel) {
            this.createView('search', 'views/record/search', {
                collection: this.collection,
                fullSelector: this.containerSelector + ' .search-container',
                searchManager: searchManager,
                disableSavePreset: true,
                filterList: this.filterList,
            }, view => {
                this.listenTo(view, 'reset', () => {});
            });
        }
    }

    setupList() {
        const viewName = this.getMetadata().get('clientDefs.' + this.scope + '.recordViews.listSelect') ||
            this.getMetadata().get('clientDefs.' + this.scope + '.recordViews.list') ||
            'views/record/list';

        const promise = this.createView('list', viewName, {
            collection: this.collection,
            fullSelector: this.containerSelector + ' .list-container',
            selectable: true,
            checkboxes: this.multiple,
            massActionsDisabled: true,
            rowActionsView: false,
            layoutName: this.layoutName,
            searchManager: this.searchManager,
            checkAllResultDisabled: !this.massRelateEnabled,
            buttonsDisabled: true,
            skipBuildRows: true,
            pagination: this.getMetadata().get(['clientDefs', this.scope, 'listPagination']) || null,
        }, view => {

            this.listenToOnce(view, 'select', model => {
                this.trigger('select', model);

                this.close();
            });

            if (this.multiple) {
                this.listenTo(view, 'check', () => {
                    if (view.checkedList.length) {
                        this.enableButton('select');
                    }
                    else {
                        this.disableButton('select');
                    }
                });

                this.listenTo(view, 'select-all-results', () => {
                    this.enableButton('select');
                });
            }

            const fetch = () => {
                this.whenRendered().then(() => {
                    Espo.Ui.notify(' ... ');

                    this.collection.fetch()
                        .then(() => Espo.Ui.notify(false));
                });
                // Timeout to make notify work.
                /*setTimeout(() => {
                    Espo.Ui.notify(' ... ');

                    this.collection.fetch()
                        .then(() => Espo.Ui.notify(false));
                }, 1);*/
            };

            if (this.options.forceSelectAllAttributes || this.forceSelectAllAttributes) {
                fetch();

                return;
            }

            view.getSelectAttributeList(selectAttributeList => {
                if (!~selectAttributeList.indexOf('name')) {
                    selectAttributeList.push('name');
                }

                const mandatorySelectAttributeList = this.options.mandatorySelectAttributeList ||
                    this.mandatorySelectAttributeList || [];

                mandatorySelectAttributeList.forEach(attribute => {
                    if (!~selectAttributeList.indexOf(attribute)) {
                        selectAttributeList.push(attribute);
                    }
                });

                if (selectAttributeList) {
                    this.collection.data.select = selectAttributeList.join(',');
                }

                fetch();
            });
        });

        this.wait(promise);
    }

    create() {
        if (this.options.triggerCreateEvent) {
            this.trigger('create');

            return;
        }

        Espo.Ui.notify(' ... ');

        const viewName = this.getMetadata()
                .get(['clientDefs', this.scope, 'modalViews', 'edit']) ||
            'views/modals/edit';

        new Promise(resolve => {
            if (this.options.createAttributesProvider) {
                this.options.createAttributesProvider().then(attributes => {
                    resolve(attributes)
                });

                return;
            }

            resolve(this.options.createAttributes || {});
        })
            .then(attributes => {
                this.createView('quickCreate', viewName, {
                    scope: this.scope,
                    fullFormDisabled: true,
                    attributes: attributes,
                }, view => {
                    view.render()
                        .then(() => Espo.Ui.notify(false));

                    this.listenToOnce(view, 'leave', () => {
                        view.close();
                        this.close();
                    });

                    this.listenToOnce(view, 'after:save', (model) => {
                        view.close();

                        this.trigger('select', model);

                        setTimeout(() => this.close(), 10);
                    });
                });
            });
    }

    actionSelect() {
        if (!this.multiple) {
            return;
        }

        const listView = this.getRecordView();

        if (listView.allResultIsChecked) {
            this.trigger('select', {
                massRelate: true,
                where: this.collection.getWhere(),
                searchParams: this.collection.data,
            });

            this.close();

            return;
        }

        const list = listView.getSelected();

        if (list.length) {
            this.trigger('select', list);
        }

        this.close();
    }

    /**
     * @protected
     * @return {?module:views/record/search}
     */
    getSearchView() {
        return this.getView('search');
    }

    /**
     * @protected
     * @return {module:views/record/list}
     */
    getRecordView() {
        return this.getView('list');
    }

    /**
     * @protected
     * @param {JQueryKeyEventObject} e
     */
    handleShortcutKeyCtrlSlash(e) {
        if (!this.searchPanel) {
            return;
        }

        const $search = this.$el.find('input.text-filter').first();

        if (!$search.length) {
            return;
        }

        e.preventDefault();
        e.stopPropagation();

        $search.focus();
    }

    /**
     * @protected
     * @param {JQueryKeyEventObject} e
     */
    handleShortcutKeyCtrlEnter(e) {
        if (!this.multiple) {
            return;
        }

        if (!this.hasAvailableActionItem('select')) {
            return;
        }

        e.stopPropagation();
        e.preventDefault();

        this.actionSelect();
    }

    /**
     * @protected
     * @param {JQueryKeyEventObject} e
     */
    handleShortcutKeyCtrlSpace(e) {
        if (!this.createButton) {
            return;
        }

        e.preventDefault();
        e.stopPropagation();

        this.create();
    }

    /**
     * @protected
     */
    handleShortcutKeyCtrlComma() {
        if (!this.getSearchView()) {
            return;
        }

        this.getSearchView().selectPreviousPreset();
    }

    /**
     * @protected
     */
    handleShortcutKeyCtrlPeriod() {
        if (!this.getSearchView()) {
            return;
        }

        this.getSearchView().selectNextPreset();
    }
}

export default SelectRecordsModalView;
PK]��-== views/modals/kanban-move-over.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ModalView from 'views/modal';

class KanbanMoveOverModalView extends ModalView {

    template = 'modals/kanban-move-over'

    /** @inheritDoc */
    backdrop = true

    data() {
        return {
            optionDataList: this.optionDataList,
        };
    }

    events = {
        /** @this KanbanMoveOverModalView */
        'click [data-action="move"]': function (e) {
            let value = $(e.currentTarget).data('value');

            this.moveTo(value);
        },
    }

    setup() {
        this.scope = this.model.entityType;

        let iconHtml = this.getHelper().getScopeColorIconHtml(this.scope);

        this.statusField = this.options.statusField;

        this.$header = $('<span>');

        this.$header.append(
            $('<span>').text(this.getLanguage().translate(this.scope, 'scopeNames'))
        );

        if (this.model.get('name')) {
            this.$header.append(' <span class="chevron-right"></span> ');
            this.$header.append(
                $('<span>').text(this.model.get('name'))
            )
        }

        this.$header.prepend(iconHtml);

        this.buttonList = [
            {
                name: 'cancel',
                label: 'Cancel'
            }
        ];

        this.optionDataList = [];

        (
            this.getMetadata()
                .get(['entityDefs', this.scope, 'fields', this.statusField, 'options']) || []
        )
            .forEach((item) => {
                this.optionDataList.push({
                    value: item,
                    label: this.getLanguage().translateOption(item, this.statusField, this.scope),
                });
            });
    }

    moveTo(status) {
        var attributes = {};

        attributes[this.statusField] = status;

        this.model
            .save(
                attributes,
                {
                    patch: true,
                    isMoveTo: true,
                }
            )
            .then(() => {
                Espo.Ui.success(this.translate('Done'));
            });

        this.close();
    }
}

// noinspection JSUnusedGlobalSymbols
export default KanbanMoveOverModalView;
PK]:�@mNmNviews/modals/related-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/modals/related-records */

import ModalView from 'views/modal';
import SearchManager from 'search-manager';
import $ from 'jquery';

/**
 * A related-list modal.
 */
class RelatedListModalView extends ModalView {

    template = 'modals/related-list'

    className = 'dialog dialog-record'
    searchPanel = true
    scope = ''
    noCreateScopeList = ['User', 'Team', 'Role', 'Portal']
    backdrop = true
    fixedHeaderHeight = true
    mandatorySelectAttributeList = null
    layoutName = 'listSmall'

    /** @inheritDoc */
    shortcutKeys = {
        /** @this RelatedListModalView */
        'Control+Space': function (e) {
            this.handleShortcutKeyCtrlSpace(e);
        },
        /** @this RelatedListModalView */
        'Control+Slash': function (e) {
            this.handleShortcutKeyCtrlSlash(e);
        },
        /** @this RelatedListModalView */
        'Control+Comma': function (e) {
            this.handleShortcutKeyCtrlComma(e);
        },
        /** @this RelatedListModalView */
        'Control+Period': function (e) {
            this.handleShortcutKeyCtrlPeriod(e);
        },
    }

    events = {
        /** @this RelatedListModalView */
        'click button[data-action="createRelated"]': function () {
            this.actionCreateRelated();
        },
        /** @this RelatedListModalView */
        'click .action': function (e) {
            const isHandled = Espo.Utils.handleAction(this, e.originalEvent, e.currentTarget);

            if (isHandled) {
                return;
            }

            this.trigger('action', e.originalEvent, e.currentTarget);
        },
    }

    setup() {
        this.primaryFilterName = this.options.primaryFilterName || null;

        this.buttonList = [
            {
                name: 'cancel',
                label: 'Close',
            }
        ];

        this.scope = this.options.scope || this.scope;

        this.defaultOrderBy = this.options.defaultOrderBy;
        this.defaultOrder = this.options.defaultOrder;

        this.panelName = this.options.panelName;
        this.link = this.options.link;

        this.defs = this.options.defs || {};

        this.filterList = this.options.filterList;
        this.filter = this.options.filter;
        this.layoutName = this.options.layoutName || this.layoutName;
        this.url = this.options.url;
        this.listViewName = this.options.listViewName;
        this.rowActionsView = this.options.rowActionsView;

        this.createDisabled = this.options.createDisabled || this.createDisabled;
        this.selectDisabled = this.options.selectDisabled || this.selectDisabled;

        this.massUnlinkDisabled = this.options.massUnlinkDisabled || this.massUnlinkDisabled;

        this.massActionRemoveDisabled = this.options.massActionRemoveDisabled ||
            this.massActionRemoveDisabled;

        this.massActionMassUpdateDisabled = this.options.massActionMassUpdateDisabled ||
            this.massActionMassUpdateDisabled;

        this.panelCollection = this.options.panelCollection;

        if (this.panelCollection) {
            this.listenTo(this.panelCollection, 'sync', (c, r, o) => {
                if (o.skipCollectionSync) {
                    return;
                }

                this.collection.fetch();
            });

            // Sync changing models.
            this.listenTo(this.panelCollection, 'change', (m, o) => {
                // Prevent change after save.
                if (o.xhr || !m.id) {
                    return;
                }

                const model = this.collection.get(m.id);

                if (!model) {
                    return;
                }

                const attributes = {};

                for (const name in m.attributes) {
                    if (m.hasChanged(name)) {
                        attributes[name] = m.attributes[name];
                    }
                }

                model.set(attributes);
            });

            if (this.model) {
                this.listenTo(this.model, 'after:unrelate', () => {
                    this.panelCollection.fetch({
                        skipCollectionSync: true,
                    });
                });
            }
        }
        else if (this.model) {
            this.listenTo(this.model, 'after:relate', () => {
                this.collection.fetch();
            });
        }

        if (this.noCreateScopeList.indexOf(this.scope) !== -1) {
            this.createDisabled = true;
        }

        this.primaryFilterName = this.filter;

        if (!this.createDisabled) {
            if (
                !this.getAcl().check(this.scope, 'create') ||
                this.getMetadata().get(['clientDefs', this.scope, 'createDisabled'])
            ) {
                this.createDisabled = true;
            }
        }

        this.unlinkDisabled = this.unlinkDisabled || this.options.unlinkDisabled || this.defs.unlinkDisabled;

        if (!this.massUnlinkDisabled) {
            if (this.unlinkDisabled || this.defs.massUnlinkDisabled || this.defs.unlinkDisabled) {
                this.massUnlinkDisabled = true;
            }

            if (!this.getAcl().check(this.model, 'edit')) {
                this.massUnlinkDisabled = true;
            }
        }

        if (!this.selectDisabled) {
            this.buttonList.unshift({
                name: 'selectRelated',
                label: 'Select',
                pullLeft: true,
            });
        }

        if (!this.createDisabled) {
            this.buttonList.unshift({
                name: 'createRelated',
                label: 'Create',
                pullLeft: true,
            });
        }

        this.$header = $('<span>');

        if (this.model) {
            if (this.model.get('name')) {
                this.$header.append(
                    $('<span>').text(this.model.get('name')),
                    ' <span class="chevron-right"></span> '
                );
            }
        }

        let title = this.options.title;

        if (title) {
            title = this.getHelper().escapeString(this.options.title)
                .replace(/@right/, '<span class="chevron-right"></span>');
        }

        this.$header.append(
            title ||
            $('<span>').text(
                this.getLanguage().translate(this.link, 'links', this.entityType)
            )
        );

        if (this.options.listViewUrl) {
            this.$header = $('<a>')
                .attr('href', this.options.listViewUrl)
                .append(this.$header);
        }

        if (
            !this.options.listViewUrl &&
            (
                !this.defs.fullFormDisabled && this.link && this.model.hasLink(this.link) ||
                this.options.fullFormUrl
            )
        ) {
            const url = this.options.fullFormUrl ||
                '#' + this.model.entityType + '/related/' + this.model.id + '/' + this.link;

            this.buttonList.unshift({
                name: 'fullForm',
                label: 'Full Form',
                onClick: () => this.getRouter().navigate(url, {trigger: true}),
            });

            this.$header = $('<a>')
                .attr('href', url)
                .append(this.$header);
        }

        const iconHtml = this.getHelper().getScopeColorIconHtml(this.scope);

        if (iconHtml) {
            this.$header = $('<span>')
                .append(iconHtml)
                .append(this.$header);
        }

        this.waitForView('list');

        if (this.searchPanel) {
            this.waitForView('search');
        }

        this.getCollectionFactory().create(this.scope, collection => {
            collection.maxSize = this.getConfig().get('recordsPerPage');
            collection.url = this.url;

            collection.setOrder(this.defaultOrderBy, this.defaultOrder, true);

            this.collection = collection;

            if (this.panelCollection) {
                this.listenTo(collection, 'change', (model) => {
                    const panelModel = this.panelCollection.get(model.id);

                    if (panelModel) {
                        panelModel.set(model.attributes);
                    }
                });

                this.listenTo(collection, 'after:mass-remove', () => {
                    this.panelCollection.fetch({
                        skipCollectionSync: true,
                    });
                });
            }

            this.setupSearch();
            this.setupList();
        });

        // If the list not yet loaded.
        this.once('close', () => {
            if (
                this.collection.lastSyncPromise &&
                this.collection.lastSyncPromise.getStatus() < 4
            ) {
                Espo.Ui.notify(false);
            }

            this.collection.abortLastFetch();
        });
    }

    setFilter(filter) {
        this.searchManager.setPrimary(filter);
    }

    /**
     * @protected
     * @return {module:views/record/search}
     */
    getSearchView() {
        return this.getView('search');
    }

    setupSearch() {
        const searchManager = this.searchManager =
            new SearchManager(this.collection, 'listSelect', null, this.getDateTime());

        searchManager.emptyOnReset = true;

        const primaryFilterName = this.primaryFilterName;

        if (primaryFilterName) {
            searchManager.setPrimary(primaryFilterName);
        }

        this.collection.where = searchManager.getWhere();

        let filterList = Espo.Utils.clone(this.getMetadata().get(['clientDefs', this.scope, 'filterList']) || []);

        if (this.filterList) {
            this.filterList.forEach(item1 => {
                let isFound = false;

                const name1 = item1.name || item1;

                if (!name1 || name1 === 'all') {
                    return;
                }

                filterList.forEach(item2 => {
                    const name2 = item2.name || item2;

                    if (name1 === name2) {
                        isFound = true;
                    }
                });

                if (!isFound) {
                    filterList.push(item1);
                }
            });
        }

        if (this.options.filtersDisabled) {
            filterList = [];
        }

        if (this.searchPanel) {
            this.createView('search', 'views/record/search', {
                collection: this.collection,
                fullSelector: this.containerSelector + ' .search-container',
                searchManager: searchManager,
                disableSavePreset: true,
                filterList: filterList,
            }, view => {
                this.listenTo(view, 'reset', () => {});
            });
        }
    }

    setupList() {
        const viewName =
            this.listViewName ||
            this.getMetadata().get(['clientDefs', this.scope, 'recordViews', 'listRelated']) ||
            this.getMetadata().get(['clientDefs', this.scope, 'recordViews', 'list']) ||
            'views/record/list';

        const promise = this.createView('list', viewName, {
            collection: this.collection,
            fullSelector: this.containerSelector + ' .list-container',
            rowActionsView: this.rowActionsView,
            layoutName: this.layoutName,
            searchManager: this.searchManager,
            buttonsDisabled: true,
            skipBuildRows: true,
            model: this.model,
            unlinkMassAction: !this.massUnlinkDisabled,
            massActionRemoveDisabled: this.massActionRemoveDisabled,
            massActionMassUpdateDisabled: this.massActionMassUpdateDisabled,
            mandatorySelectAttributeList: this.mandatorySelectAttributeList,
            rowActionsOptions: {
                unlinkDisabled: this.unlinkDisabled,
            },
            pagination: this.getConfig().get('listPagination') ||
                this.getMetadata().get(['clientDefs', this.scope, 'listPagination']) ||
                null,
        }, view => {

            this.listenToOnce(view, 'select', model => {
                this.trigger('select', model);

                this.close();
            });

            if (this.multiple) {
                this.listenTo(view, 'check', () => {
                    view.checkedList.length ?
                        this.enableButton('select') :
                        this.disableButton('select');
                });

                this.listenTo(view, 'select-all-results', () => this.enableButton('select'));
            }

            const fetch = () => {
                this.whenRendered().then(() => {
                    Espo.Ui.notify(' ... ');

                    this.collection.fetch()
                        .then(() => Espo.Ui.notify(false));
                });
                // Timeout to make notify work.
                /*setTimeout(() => {
                    Espo.Ui.notify(' ... ');

                    this.collection.fetch()
                        .then(() => Espo.Ui.notify(false));
                }, 1);*/
            };

            if (this.options.forceSelectAllAttributes || this.forceSelectAllAttributes) {
                fetch();

                return;
            }

            view.getSelectAttributeList(selectAttributeList => {
                if (!~selectAttributeList.indexOf('name')) {
                    selectAttributeList.push('name');
                }

                const mandatorySelectAttributeList = this.options.mandatorySelectAttributeList ||
                    this.mandatorySelectAttributeList || [];

                mandatorySelectAttributeList.forEach(attribute => {
                    if (!~selectAttributeList.indexOf(attribute)) {
                        selectAttributeList.push(attribute);
                    }
                });

                if (selectAttributeList) {
                    this.collection.data.select = selectAttributeList.join(',');
                }

                fetch();
            });
        });

        this.wait(promise);
    }

    // noinspection JSUnusedGlobalSymbols
    actionUnlinkRelated(data) {
        const id = data.id;

        this.confirm({
            message: this.translate('unlinkRecordConfirmation', 'messages'),
            confirmText: this.translate('Unlink'),
        }, () => {
            Espo.Ui.notify(' ... ');

            Espo.Ajax.deleteRequest(this.collection.url, {id: id}).then(() => {
                Espo.Ui.success(this.translate('Unlinked'));

                this.collection.fetch();

                this.model.trigger('after:unrelate');
                this.model.trigger('after:unrelate:' + this.link);
            });
        });
    }

    actionCreateRelated() {
        // noinspection JSUnresolvedReference
        const actionName = this.defs.createAction || 'createRelated';
        const methodName = 'action' + Espo.Utils.upperCaseFirst(actionName);

        let p = this.getParentView();

        let view = null;

        while (p) {
            if (p[methodName]) {
                view = p;

                break;
            }

            p = p.getParentView();
        }

        p[methodName]({
            link: this.link,
            scope: this.scope,
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionSelectRelated() {
        // noinspection JSUnresolvedReference
        const actionName = this.defs.selectAction || 'selectRelated';
        const methodName = 'action' + Espo.Utils.upperCaseFirst(actionName);

        let p = this.getParentView();

        let view = null;

        while (p) {
            if (p[methodName]) {
                view = p;

                break;
            }

            p = p.getParentView();
        }

        p[methodName]({
            link: this.link,
            primaryFilterName: this.defs.selectPrimaryFilterName,
            boolFilterList: this.defs.selectBoolFilterList,
            massSelect: this.defs.massSelect,
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionRemoveRelated(data) {
        const id = data.id;

        this.confirm({
            message: this.translate('removeRecordConfirmation', 'messages'),
            confirmText: this.translate('Remove'),
        }, () => {
            const model = this.collection.get(id);

            Espo.Ui.notify(' ... ');

            model
                .destroy()
                .then(() => {
                    Espo.Ui.success(this.translate('Removed'));

                    this.collection.fetch();

                    this.model.trigger('after:unrelate');
                    this.model.trigger('after:unrelate:' + this.link);
                });
        });
    }

    /**
     * @protected
     * @param {JQueryKeyEventObject} e
     */
    handleShortcutKeyCtrlSlash(e) {
        if (!this.searchPanel) {
            return;
        }

        const $search = this.$el.find('input.text-filter').first();

        if (!$search.length) {
            return;
        }

        e.preventDefault();
        e.stopPropagation();

        $search.focus();
    }

    /**
     * @protected
     * @param {JQueryKeyEventObject} e
     */
    handleShortcutKeyCtrlSpace(e) {
        if (this.createDisabled) {
            return;
        }

        if (this.buttonList.findIndex(item => item.name === 'createRelated' && !item.hidden) === -1) {
            return;
        }

        e.preventDefault();
        e.stopPropagation();

        this.actionCreateRelated();
    }

    /**
     * @protected
     */
    handleShortcutKeyCtrlComma() {
        if (!this.getSearchView()) {
            return;
        }

        this.getSearchView().selectPreviousPreset();
    }

    /**
     * @protected
     */
    handleShortcutKeyCtrlPeriod() {
        if (!this.getSearchView()) {
            return;
        }

        this.getSearchView().selectNextPreset();
    }
}

export default RelatedListModalView;
PK]��I��views/modals/followers-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import RelatedListModalView from 'views/modals/related-list';

class FollowersListModalView extends RelatedListModalView {

    massActionRemoveDisabled = true
    massActionMassUpdateDisabled = true
    mandatorySelectAttributeList = ['type']

    setup() {
        if (
            !this.getUser().isAdmin() &&
            this.getAcl().getPermissionLevel('followerManagementPermission') === 'no' &&
            this.getAcl().getPermissionLevel('portalPermission') === 'no'
        ) {
            this.unlinkDisabled = true;
        }

        super.setup();
    }

    actionSelectRelated() {
        let p = this.getParentView();

        let view = null;

        while (p) {
            // noinspection JSUnresolvedReference
            if (p.actionSelectRelated) {
                view = p;

                break;
            }

            p = p.getParentView();
        }

        let filter = 'active';

        if (
            !this.getUser().isAdmin() &&
            this.getAcl().getPermissionLevel('followerManagementPermission') === 'no' &&
            this.getAcl().getPermissionLevel('portalPermission') === 'yes'
        ) {
            filter = 'activePortal';
        }

        // noinspection JSUnresolvedReference
        p.actionSelectRelated({
            link: this.link,
            primaryFilterName: filter,
            massSelect: false,
            foreignEntityType: 'User',
            viewKey: 'selectFollowers',
        });
    }
}

export default FollowersListModalView;
PK]�JuVy
y
views/modals/duplicate.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ModalView from 'views/modal';

class DuplicateModalView extends ModalView {

    template = 'modals/duplicate'

    cssName = 'duplicate-modal'

    data() {
        return {
            scope: this.scope,
            duplicates: this.duplicates,
        };
    }

    setup() {
        let saveLabel = 'Save';

        if (this.model && this.model.isNew()) {
            saveLabel = 'Create';
        }

        this.buttonList = [
            {
                name: 'save',
                label: saveLabel,
                style: 'danger',
                onClick: dialog => {
                    this.trigger('save');

                    dialog.close();
                },
            },
            {
                name: 'cancel',
                label: 'Cancel',
            },
        ];

        this.scope = this.options.scope;
        this.duplicates = this.options.duplicates;

        if (this.scope) {
            this.setupRecord();
        }
    }

    setupRecord() {
        let promise = new Promise(resolve => {
            this.getHelper().layoutManager.get(this.scope, 'listSmall', layout => {
                layout = Espo.Utils.cloneDeep(layout);
                layout.forEach(item => item.notSortable = true);

                this.getCollectionFactory().create(this.scope)
                    .then(collection => {
                        collection.add(this.duplicates);

                        this.createView('record', 'views/record/list', {
                            selector: '.list-container',
                            collection: collection,
                            listLayout: layout,
                            buttonsDisabled: true,
                            massActionsDisabled: true,
                            rowActionsDisabled: true,
                        });

                        resolve();
                    });
            });
        })

        this.wait(promise);
    }
}

export default DuplicateModalView;
PK]U�%views/modals/mass-convert-currency.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ModalView from 'views/modal';
import Model from 'model';
import Helper from 'helpers/mass-action';

class MassConvertCurrencyModalView extends ModalView {

    template = 'modals/mass-convert-currency'

    className = 'dialog dialog-record'

    buttonList = [
        {
            name: 'cancel',
            label: 'Cancel',
        }
    ]

    data() {
        return {};
    }

    setup() {
        this.$header = $('<span>')
            .append(
                $('<span>').text(this.translate(this.options.entityType, 'scopeNamesPlural')),
                ' <span class="chevron-right"></span> ',
                $('<span>').text(this.translate('convertCurrency', 'massActions'))
            )

        this.addButton({
            name: 'convert',
            text: this.translate('Update'),
            style: 'danger'
        }, true);

        let model = this.model = new Model();

        model.set('currency', this.getConfig().get('defaultCurrency'));
        model.set('baseCurrency', this.getConfig().get('baseCurrency'));
        model.set('currencyRates', this.getConfig().get('currencyRates'));
        model.set('currencyList', this.getConfig().get('currencyList'));

        this.createView('currency', 'views/fields/enum', {
            model: model,
            params: {
                options: this.getConfig().get('currencyList')
            },
            name: 'currency',
            selector: '.field[data-name="currency"]',
            mode: 'edit',
            labelText: this.translate('Convert to')
        });

        this.createView('baseCurrency', 'views/fields/enum', {
            model: model,
            params: {
                options: this.getConfig().get('currencyList')
            },
            name: 'baseCurrency',
            selector: '.field[data-name="baseCurrency"]',
            mode: 'detail',
            labelText: this.translate('baseCurrency', 'fields', 'Settings'),
            readOnly: true
        });

        this.createView('currencyRates', 'views/settings/fields/currency-rates', {
            model: model,
            name: 'currencyRates',
            selector: '.field[data-name="currencyRates"]',
            mode: 'edit',
            labelText: this.translate('currencyRates', 'fields', 'Settings')
        });
    }

    /**
     * @param {string} field
     * @return {module:views/fields/base}
     */
    getFieldView(field) {
        return this.getView(field);
    }

    // noinspection JSUnusedGlobalSymbols
    actionConvert() {
        this.disableButton('convert');

        this.getFieldView('currency').fetchToModel();
        this.getFieldView('currencyRates').fetchToModel();

        let currency = this.model.get('currency');
        let currencyRates = this.model.get('currencyRates');

        let hasWhere = !this.options.ids || this.options.ids.length === 0;

        let helper = new Helper(this);

        let idle = hasWhere && helper.checkIsIdle(this.options.totalCount);

        Espo.Ajax.postRequest('MassAction', {
                entityType: this.options.entityType,
                action: 'convertCurrency',
                params: {
                   ids: this.options.ids || null,
                   where: hasWhere ? this.options.where : null,
                   searchParams: hasWhere ? this.options.searchParams : null,
                },
                data: {
                    fieldList: this.options.fieldList || null,
                    currency: currency,
                    targetCurrency: currency,
                    rates: currencyRates,
                },
                idle: idle,
            })
            .then(result => {
                if (result.id) {
                    helper
                        .process(result.id, 'convertCurrency')
                        .then(view => {
                            this.listenToOnce(view, 'close', () => this.close());

                            this.listenToOnce(view, 'success', result => {
                                this.trigger('after:update', {
                                    count: result.count,
                                    idle: true,
                                });
                            });
                        });

                    return;
                }

                this.trigger('after:update', {count: result.count});

                this.close();
            })
            .catch(() => {
                this.enableButton('convert');
            });
    }
}

export default MassConvertCurrencyModalView;
PK]'��00views/modals/edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/modals/edit */

import ModalView from 'views/modal';
import Backbone from 'backbone';

/**
 * A quick edit modal.
 */
class EditModalView extends ModalView {

    template = 'modals/edit'

    cssName = 'edit-modal'
    saveDisabled = false
    fullFormDisabled = false
    editView = null
    escapeDisabled = true
    className = 'dialog dialog-record'
    sideDisabled = false
    bottomDisabled = false

    shortcutKeys = {
        /** @this EditModalView */
        'Control+Enter': function (e) {
            if (this.saveDisabled) {
                return;
            }

            if (this.buttonList.findIndex(item => item.name === 'save' && !item.hidden) === -1) {
                return;
            }

            e.preventDefault();
            e.stopPropagation();

            this.actionSave();
        },
        /** @this EditModalView */
        'Control+KeyS': function (e) {
            if (this.saveDisabled) {
                return;
            }

            if (this.buttonList.findIndex(item => item.name === 'save' && !item.hidden) === -1) {
                return;
            }

            e.preventDefault();
            e.stopPropagation();

            this.actionSaveAndContinueEditing();
        },
        /** @this EditModalView */
        'Escape': function (e) {
            if (this.saveDisabled) {
                return;
            }

            e.stopPropagation();
            e.preventDefault();

            let focusedFieldView = this.getRecordView().getFocusedFieldView();

            if (focusedFieldView) {
                this.model.set(focusedFieldView.fetch(), {skipReRender: true});
            }

            if (this.getRecordView().isChanged) {
                this.confirm(this.translate('confirmLeaveOutMessage', 'messages'))
                    .then(() => this.actionClose());

                return;
            }

            this.actionClose();
        },
        /** @this EditModalView */
        'Control+Backslash': function (e) {
            this.getRecordView().handleShortcutKeyControlBackslash(e);
        },
    }

    setup() {
        this.buttonList = [];

        if ('saveDisabled' in this.options) {
            this.saveDisabled = this.options.saveDisabled;
        }

        if (!this.saveDisabled) {
            this.buttonList.push({
                name: 'save',
                label: 'Save',
                style: 'primary',
                title: 'Ctrl+Enter',
            });
        }

        this.fullFormDisabled = this.options.fullFormDisabled || this.fullFormDisabled;

        this.layoutName = this.options.layoutName || this.layoutName;

        if (!this.fullFormDisabled) {
            this.buttonList.push({
                name: 'fullForm',
                label: 'Full Form',
            });
        }

        this.buttonList.push({
            name: 'cancel',
            label: 'Cancel',
            title: 'Esc',
        });

        this.scope = this.scope || this.options.scope;
        this.entityType = this.options.entityType || this.scope;
        this.id = this.options.id;

        this.headerHtml = this.composeHeaderHtml();

        this.sourceModel = this.model;

        this.waitForView('edit');

        this.getModelFactory().create(this.entityType, (model) => {
            if (this.id) {
                if (this.sourceModel) {
                    model = this.model = this.sourceModel.clone();
                }
                else {
                    this.model = model;

                    model.id = this.id;
                }

                model
                    .fetch()
                    .then(() => {
                        this.createRecordView(model);
                    });

                return;
            }

            this.model = model;

            if (this.options.relate) {
                model.setRelate(this.options.relate);
            }

            if (this.options.attributes) {
                model.set(this.options.attributes);
            }

            this.createRecordView(model);
        });
    }

    /**
     * @param {module:model} model
     * @param {function} [callback]
     */
    createRecordView(model, callback) {
        let viewName =
            this.editView ||
            this.getMetadata().get(['clientDefs', model.entityType, 'recordViews', 'editSmall']) ||
            this.getMetadata().get(['clientDefs', model.entityType, 'recordViews', 'editQuick']) ||
            'views/record/edit-small';

        let options = {
            model: model,
            fullSelector: this.containerSelector + ' .edit-container',
            type: 'editSmall',
            layoutName: this.layoutName || 'detailSmall',
            buttonsDisabled: true,
            sideDisabled: this.sideDisabled,
            bottomDisabled: this.bottomDisabled,
            focusForCreate: this.options.focusForCreate,
            exit: () => {},
        };

        this.handleRecordViewOptions(options);

        this.createView('edit', viewName, options, callback)
            .then(view => {
                this.listenTo(view, 'before:save', () => this.trigger('before:save', model));

                if (this.options.relate && ('link' in this.options.relate)) {
                    let link = this.options.relate.link;

                    if (
                        model.hasField(link) &&
                        ['link'].includes(model.getFieldType(link))
                    ) {
                        view.setFieldReadOnly(link);
                    }
                }
            });
    }

    handleRecordViewOptions(options) {}

    /**
     * @return {module:views/record/edit}
     */
    getRecordView() {
        return this.getView('edit');
    }

    onBackdropClick() {
        if (this.getRecordView().isChanged) {
            return;
        }

        this.close();
    }

    /**
     * @protected
     * @return {string}
     */
    composeHeaderHtml() {
        let html;

        if (!this.id) {
            html = $('<span>')
                .text(this.getLanguage().translate('Create ' + this.scope, 'labels', this.scope))
                .get(0).outerHTML;
        }
        else {
            let text = this.getLanguage().translate('Edit') + ' · ' +
                this.getLanguage().translate(this.scope, 'scopeNames');

            html = $('<span>')
                .text(text)
                .get(0).outerHTML;
        }

        if (!this.fullFormDisabled) {
            let url = this.id ?
                '#' + this.scope + '/edit/' + this.id :
                '#' + this.scope + '/create';

            html =
                $('<a>')
                    .attr('href', url)
                    .addClass('action')
                    .attr('title', this.translate('Full Form'))
                    .attr('data-action', 'fullForm')
                    .append(html)
                    .get(0).outerHTML;
        }

        html = this.getHelper().getScopeColorIconHtml(this.scope) + html;

        return html;
    }

    actionSave(data) {
        data = data || {};

        let editView = this.getRecordView();

        let model = editView.model;

        let $buttons = this.dialog.$el.find('.modal-footer button');

        $buttons.addClass('disabled').attr('disabled', 'disabled');

        editView
            .save()
            .then(() => {
                const wasNew = !this.id;

                if (wasNew) {
                    this.id = model.id;
                }

                this.trigger('after:save', model, {bypassClose: data.bypassClose});

                if (!data.bypassClose) {
                    this.dialog.close();

                    if (wasNew) {
                        const url = '#' + this.scope + '/view/' + model.id;
                        const name = model.get('name') || this.model.id;

                        const msg = this.translate('Created') + '\n' +
                            `[${name}](${url})`;

                        Espo.Ui.notify(msg, 'success', 4000, {suppress: true});
                    }

                    return;
                }

                this.$el.find('.modal-header .modal-title-text')
                    .html(this.composeHeaderHtml());

                $buttons.removeClass('disabled').removeAttr('disabled');
            })
            .catch(() => {
                $buttons.removeClass('disabled').removeAttr('disabled');
            })
    }

    actionSaveAndContinueEditing() {
        this.actionSave({bypassClose: true});
    }

    // noinspection JSUnusedGlobalSymbols
    actionFullForm() {
        let url;
        let router = this.getRouter();

        let attributes;
        let model;
        let options;

        if (!this.id) {
            url = '#' + this.scope + '/create';

            attributes = this.getRecordView().fetch();
            model = this.getRecordView().model;

            attributes = {...attributes, ...model.getClonedAttributes()};

            options = {
                attributes: attributes,
                relate: this.options.relate,
                returnUrl: this.options.returnUrl || Backbone.history.fragment,
                returnDispatchParams: this.options.returnDispatchParams || null,
            };

            if (this.options.rootUrl) {
                options.rootUrl = this.options.rootUrl;
            }

            setTimeout(() => {
                router.dispatch(this.scope, 'create', options);
                router.navigate(url, {trigger: false});
            }, 10);
        }
        else {
            url = '#' + this.scope + '/edit/' + this.id;

            attributes = this.getRecordView().fetch();
            model = this.getRecordView().model;

            attributes = {...attributes, ...model.getClonedAttributes()};

            options = {
                attributes: attributes,
                returnUrl: this.options.returnUrl || Backbone.history.fragment,
                returnDispatchParams: this.options.returnDispatchParams || null,
                model: this.sourceModel,
                id: this.id,
            };

            if (this.options.rootUrl) {
                options.rootUrl = this.options.rootUrl;
            }

            setTimeout(() => {
                router.dispatch(this.scope, 'edit', options);
                router.navigate(url, {trigger: false});
            }, 10);
        }

        this.trigger('leave');
        this.dialog.close();
    }
}

export default EditModalView;
PK]A�s�l
l
 views/modals/convert-currency.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import MassConvertCurrencyModalView from 'views/modals/mass-convert-currency';

class ConvertCurrencyModalView extends MassConvertCurrencyModalView {

    setup() {
        super.setup();

        this.headerText = this.translate('convertCurrency', 'massActions');
    }

    actionConvert() {
        this.disableButton('convert');

        this.getFieldView('currency').fetchToModel();
        this.getFieldView('currencyRates').fetchToModel();

        let currency = this.model.get('currency');
        let currencyRates = this.model.get('currencyRates');

        Espo.Ajax
            .postRequest('Action', {
                entityType: this.options.entityType,
                action: 'convertCurrency',
                id: this.options.model.id,
                data: {
                    targetCurrency: currency,
                    rates: currencyRates,
                    fieldList: this.options.fieldList || null,
                },
            })
            .then(attributes => {
                this.trigger('after:update', attributes);

                this.close();
            })
            .catch(() => {
                this.enableButton('convert');
            });
    }
}

export default ConvertCurrencyModalView;
PK]�G,���views/modals/array-field-add.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ModalView from 'views/modal';

class ArrayFieldAddModalView extends ModalView {

    template = 'modals/array-field-add'

    cssName = 'add-modal'
    backdrop = true

    data() {
        return {
            optionList: this.optionList,
            translatedOptions: this.translations,
        };
    }

    events = {
        /** @this ArrayFieldAddModalView */
        'click .add': function (e) {
            let value = $(e.currentTarget).attr('data-value');

            this.trigger('add', value);
        },
        /** @this ArrayFieldAddModalView */
        'click input[type="checkbox"]': function (e) {
            let value = $(e.currentTarget).attr('data-value');

            if (e.target.checked) {
                this.checkedList.push(value);
            } else {
                let index = this.checkedList.indexOf(value);

                if (index !== -1) {
                    this.checkedList.splice(index, 1);
                }
            }

            this.checkedList.length ?
                this.enableButton('select') :
                this.disableButton('select');
        },
        /** @this ArrayFieldAddModalView */
        'keyup input[data-name="quick-search"]': function (e) {
            this.processQuickSearch(e.currentTarget.value);
        },
    }

    setup() {
        this.headerText = this.translate('Add Item');
        this.checkedList = [];
        this.translations = Espo.Utils.clone(this.options.translatedOptions || {});
        this.optionList = this.options.options || [];

        this.optionList.forEach(item => {
            if (item in this.translations) {
                return;
            }

            this.translations[item] = item;
        });

        this.buttonList = [
            {
                name: 'select',
                style: 'danger',
                label: 'Select',
                disabled: true,
                onClick: () => {
                    this.trigger('add-mass', this.checkedList);
                },
            },
            {
                name: 'cancel',
                label: 'Cancel',
            },
        ];
    }

    afterRender() {
        this.$noData = this.$el.find('.no-data');

        setTimeout(() => {
            this.$el.find('input[data-name="quick-search"]').focus();
        }, 100);
    }

    processQuickSearch(text) {
        text = text.trim();

        let $noData = this.$noData;

        $noData.addClass('hidden');

        if (!text) {
            this.$el.find('ul .list-group-item').removeClass('hidden');

            return;
        }

        let matchedList = [];

        let lowerCaseText = text.toLowerCase();

        this.optionList.forEach(item => {
            let label = this.translations[item].toLowerCase();

            for (let word of label.split(' ')) {
                let matched = word.indexOf(lowerCaseText) === 0;

                if (matched) {
                    matchedList.push(item);

                    return;
                }
            }
        });

        if (matchedList.length === 0) {
            this.$el.find('ul .list-group-item').addClass('hidden');

            $noData.removeClass('hidden');

            return;
        }

        this.optionList.forEach(item => {
            let $row = this.$el.find(`ul .list-group-item[data-name="${item}"]`);

            if (!~matchedList.indexOf(item)) {
                $row.addClass('hidden');

                return;
            }

            $row.removeClass('hidden');
        });
    }
}

export default ArrayFieldAddModalView;
PK]&���views/modals/mass-action.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ModalView from 'views/modal';
import Model from 'model';

class MassActionModalView extends ModalView {

    template = 'modals/mass-action'

    className = 'dialog dialog-record'
    checkInterval = 4000

    data() {
        return {
            infoText: this.translate('infoText', 'messages', 'MassAction'),
        };
    }

    setup() {
        this.action = this.options.action;
        this.id = this.options.id;
        this.status = 'Pending';

        this.headerText =
            this.translate('Mass Action', 'scopeNames') + ': ' +
            this.translate(this.action, 'massActions', this.options.scope);

        this.model = new Model();
        this.model.name = 'MassAction';

        this.model.setDefs({
            fields: {
                'status': {
                    type: 'enum',
                    readOnly: true,
                    options: [
                        'Pending',
                        'Running',
                        'Success',
                        'Failed',
                    ],
                    style: {
                        'Success': 'success',
                        'Failed': 'danger',
                    },
                },
                'processedCount': {
                    type: 'int',
                    readOnly: true,
                },
            }
        });

        this.model.set({
            status: this.status,
            processedCount: null,
        });

        this.createView('record', 'views/record/edit-for-modal', {
            scope: 'None',
            model: this.model,
            selector: '.record',
            detailLayout: [
                {
                    rows: [
                        [
                            {
                                name: 'status',
                                labelText: this.translate('status', 'fields', 'MassAction'),
                            },
                            {
                                name: 'processedCount',
                                labelText: this.translate('processedCount', 'fields', 'MassAction'),
                            },
                        ],
                    ],
                },
            ],
        });

        this.on('close', () => {
            let status = this.model.get('status');

            if (
                status !== 'Pending' &&
                status !== 'Running'
            ) {
                return;
            }

            Espo.Ajax.postRequest(`MassAction/${this.id}/subscribe`);
        });

        this.checkStatus();
    }

    checkStatus() {
        Espo.Ajax
            .getRequest(`MassAction/${this.id}/status`)
            .then(response => {
                let status = response.status;

                this.model.set('status', status);

                if (status === 'Pending' || status === 'Running') {
                    setTimeout(() => this.checkStatus(), this.checkInterval);

                    return;
                }

                this.model.set({
                    processedCount: response.processedCount,
                });

                if (status === 'Success') {
                    this.trigger('success', {
                        count: response.processedCount,
                    });
                }

                if (this.$el) {
                    this.$el.find('.info-text').addClass('hidden');
                }
            });
    }
}

export default MassActionModalView;
PK]�PXk		views/modals/select-template.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import SelectRecordsModalView from 'views/modals/select-records';

class SelectTemplateModalView extends SelectRecordsModalView {

    multiple = false
    createButton = false
    searchPanel = false
    scope = 'Template'
    backdrop = true

    setupSearch() {
        super.setupSearch();

        this.searchManager.setAdvanced({
            entityType: {
                type: 'equals',
                value: this.options.entityType,
            },
        });

        this.collection.where = this.searchManager.getWhere();
    }

    afterRender() {
        super.afterRender();

        let firstLinkElement = this.$el.find('a.link').first().get(0);

        if (firstLinkElement) {
            // noinspection JSUnresolvedReference
            setTimeout(() => firstLinkElement.focus({preventScroll: true}), 10);
        }
    }
}

export default SelectTemplateModalView;
PK]$�
��T�Tviews/modals/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/modals/detail */

import ModalView from 'views/modal';
import ActionItemSetup from 'helpers/action-item-setup';
import Backbone from 'backbone';

/**
 * A quick view modal.
 */
class DetailModalView extends ModalView {

    template = 'modals/detail'

    cssName = 'detail-modal'
    className = 'dialog dialog-record'
    editDisabled = false
    fullFormDisabled = false
    detailView = null
    removeDisabled = true
    backdrop = true
    fitHeight = true
    sideDisabled = false
    bottomDisabled = false
    fixedHeaderHeight = true
    flexibleHeaderFontSize = true
    duplicateAction = false

    shortcutKeys = {
        /** @this DetailModalView */
        'Control+Space': function (e) {
            if (this.editDisabled) {
                return;
            }

            if (e.target.tagName === 'TEXTAREA' || e.target.tagName === 'INPUT') {
                return;
            }

            if (this.buttonList.findIndex(item => item.name === 'edit') === -1) {
                return;
            }

            e.stopPropagation();
            e.preventDefault();

            this.actionEdit()
                .then(view => {
                    view.$el
                        .find('.form-control:not([disabled])')
                        .first()
                        .focus();
                });
        },
        /** @this DetailModalView */
        'Control+Backslash': function (e) {
            this.getRecordView().handleShortcutKeyControlBackslash(e);
        },
        /** @this DetailModalView */
        'Control+ArrowLeft': function (e) {
            this.handleShortcutKeyControlArrowLeft(e);
        },
        /** @this DetailModalView */
        'Control+ArrowRight': function (e) {
            this.handleShortcutKeyControlArrowRight(e);
        },
    }

    setup() {
        this.scope = this.scope || this.options.scope;
        this.id = this.options.id;

        this.buttonList = [];

        if ('editDisabled' in this.options) {
            this.editDisabled = this.options.editDisabled;
        }

        if ('removeDisabled' in this.options) {
            this.removeDisabled = this.options.removeDisabled;
        }

        this.editDisabled = this.getMetadata().get(['clientDefs', this.scope, 'editDisabled']) ||
            this.editDisabled;
        this.removeDisabled = this.getMetadata().get(['clientDefs', this.scope, 'removeDisabled']) ||
            this.removeDisabled;

        this.fullFormDisabled = this.options.fullFormDisabled || this.fullFormDisabled;
        this.layoutName = this.options.layoutName || this.layoutName;

        this.setupRecordButtons();

        if (this.model) {
            this.controlRecordButtonsVisibility();
        }

        if (!this.fullFormDisabled) {
            this.buttonList.push({
                name: 'fullForm',
                label: 'Full Form',
            });
        }

        this.buttonList.push({
            name: 'cancel',
            label: 'Close',
            title: 'Esc',
        });

        if (this.model && this.model.collection && !this.navigateButtonsDisabled) {
            this.buttonList.push({
                name: 'previous',
                html: '<span class="fas fa-chevron-left"></span>',
                title: this.translate('Previous Entry'),
                position: 'right',
                className: 'btn-icon',
                style: 'text',
                disabled: true,
            });

            this.buttonList.push({
                name: 'next',
                html: '<span class="fas fa-chevron-right"></span>',
                title: this.translate('Next Entry'),
                position: 'right',
                className: 'btn-icon',
                style: 'text',
                disabled: true,
            });

            this.indexOfRecord = this.model.collection.indexOf(this.model);
        }
        else {
            this.navigateButtonsDisabled = true;
        }

        this.waitForView('record');

        this.sourceModel = this.model;

        this.getModelFactory().create(this.scope).then(model => {
            if (!this.sourceModel) {
                this.model = model;
                this.model.id = this.id;

                this.setupAfterModelCreated();

                this.listenTo(this.model, 'sync', () => {
                    this.controlRecordButtonsVisibility();

                    this.trigger('model-sync');
                });

                this.listenToOnce(this.model, 'sync', () => {
                    this.setupActionItems();
                    this.createRecordView();
                });

                this.model.fetch();

                return;
            }

            this.model = this.sourceModel.clone();
            this.model.collection = this.sourceModel.collection.clone();

            this.setupAfterModelCreated();

            this.listenTo(this.model, 'change', () => {
                this.sourceModel.set(this.model.getClonedAttributes());
            });

            this.listenTo(this.model, 'sync', () => {
                this.controlRecordButtonsVisibility();

                this.trigger('model-sync');
            });

            this.once('after:render', () => {
                this.model.fetch();
            });

            this.setupActionItems();
            this.createRecordView();
        });

        this.listenToOnce(this.getRouter(), 'routed', () => {
            this.remove();
        });

        if (this.duplicateAction && this.getAcl().checkScope(this.scope, 'create')) {
            this.addDropdownItem({
                name: 'duplicate',
                label: 'Duplicate',
            });
        }
    }

    /** @private */
    setupActionItems() {
        let actionItemSetup = new ActionItemSetup(
            this.getMetadata(),
            this.getHelper(),
            this.getAcl(),
            this.getLanguage()
        );

        actionItemSetup.setup(
            this,
            'modalDetail',
            promise => this.wait(promise),
            item => this.addDropdownItem(item),
            name => this.showActionItem(name),
            name => this.hideActionItem(name),
            {listenToViewModelSync: true}
        );
    }

    /**
     * @protected
     */
    setupAfterModelCreated() {}

    /**
     * @protected
     */
    setupRecordButtons() {
        if (!this.removeDisabled) {
            this.addRemoveButton();
        }

        if (!this.editDisabled) {
            this.addEditButton();
        }
    }

    controlRecordButtonsVisibility() {
        if (this.getAcl().check(this.model, 'edit')) {
            this.showButton('edit');
        } else {
            this.hideButton('edit');
        }

        if (this.getAcl().check(this.model, 'delete')) {
            this.showActionItem('remove');
        } else {
            this.hideActionItem('remove');
        }
    }

    addEditButton() {
        this.addButton({
            name: 'edit',
            label: 'Edit',
            title: 'Ctrl+Space',
        }, true);
    }

    removeEditButton() {
        this.removeButton('edit');
    }

    addRemoveButton() {
        this.addDropdownItem({
            name: 'remove',
            label: 'Remove',
        });
    }

    removeRemoveButton() {
        this.removeButton('remove');
    }

    getScope() {
        return this.scope;
    }

    createRecordView(callback) {
        let model = this.model;
        let scope = this.getScope();

        this.headerHtml = '';

        this.headerHtml += $('<span>')
            .text(this.getLanguage().translate(scope, 'scopeNames'))
            .get(0).outerHTML;

        if (model.get('name')) {
            this.headerHtml += ' ' +
                $('<span>')
                    .addClass('chevron-right')
                    .get(0).outerHTML;

            this.headerHtml += ' ' +
                $('<span>')
                    .text(model.get('name'))
                    .get(0).outerHTML;
        }

        if (!this.fullFormDisabled) {
            let url = '#' + scope + '/view/' + this.id;

            this.headerHtml =
                $('<a>')
                    .attr('href', url)
                    .addClass('action font-size-flexible')
                    .attr('title', this.translate('Full Form'))
                    .attr('data-action', 'fullForm')
                    .append(this.headerHtml)
                    .get(0).outerHTML;
        }

        this.headerHtml = this.getHelper().getScopeColorIconHtml(this.scope) + this.headerHtml;

        if (!this.editDisabled) {
            let editAccess = this.getAcl().check(model, 'edit', true);

            if (editAccess) {
                this.showButton('edit');
            } else {
                this.hideButton('edit');

                if (editAccess === null) {
                    this.listenToOnce(model, 'sync', () => {
                        if (this.getAcl().check(model, 'edit')) {
                            this.showButton('edit');
                        }
                    });
                }
            }
        }

        if (!this.removeDisabled) {
            var removeAccess = this.getAcl().check(model, 'delete', true);

            if (removeAccess) {
                this.showButton('remove');
            }
            else {
                this.hideButton('remove');

                if (removeAccess === null) {
                    this.listenToOnce(model, 'sync', () => {
                        if (this.getAcl().check(model, 'delete')) {
                            this.showButton('remove');
                        }
                    });
                }
            }
        }

        let viewName =
            this.detailViewName ||
            this.detailView ||
            this.getMetadata().get(['clientDefs', model.entityType, 'recordViews', 'detailSmall']) ||
            this.getMetadata().get(['clientDefs', model.entityType, 'recordViews', 'detailQuick']) ||
            'views/record/detail-small';

        let options = {
            model: model,
            fullSelector: this.containerSelector + ' .record-container',
            type: 'detailSmall',
            layoutName: this.layoutName || 'detailSmall',
            buttonsDisabled: true,
            inlineEditDisabled: true,
            sideDisabled: this.sideDisabled,
            bottomDisabled: this.bottomDisabled,
            exit: function () {},
        };

        this.createView('record', viewName, options, callback);
    }

    /**
     * @return {module:views/record/detail}
     */
    getRecordView() {
        return this.getView('record');
    }

    afterRender() {
        super.afterRender();

        setTimeout(() => {
            this.$el.children(0).scrollTop(0);
        }, 50);

        if (!this.navigateButtonsDisabled) {
            this.controlNavigationButtons();
        }
    }

    controlNavigationButtons() {
        let recordView = this.getRecordView();

        if (!recordView) {
            return;
        }

        let indexOfRecord = this.indexOfRecord;

        let previousButtonEnabled = false;
        let nextButtonEnabled = false;

        if (indexOfRecord > 0) {
            previousButtonEnabled = true;
        }

        if (indexOfRecord < this.model.collection.total - 1) {
            nextButtonEnabled = true;
        }
        else {
            if (this.model.collection.total === -1) {
                nextButtonEnabled = true;
            } else if (this.model.collection.total === -2) {
                if (indexOfRecord < this.model.collection.length - 1) {
                    nextButtonEnabled = true;
                }
            }
        }

        if (previousButtonEnabled) {
            this.enableButton('previous');
        } else {
            this.disableButton('previous');
        }

        if (nextButtonEnabled) {
            this.enableButton('next');
        } else {
             this.disableButton('next');
        }
    }

    switchToModelByIndex(indexOfRecord) {
        if (!this.model.collection) {
            return;
        }

        let previousModel = this.model;

        this.sourceModel = this.model.collection.at(indexOfRecord);

        if (!this.sourceModel) {
            throw new Error("Model is not found in collection by index.");
        }

        this.indexOfRecord = indexOfRecord;

        this.id = this.sourceModel.id;
        this.scope = this.sourceModel.entityType;

        this.model = this.sourceModel.clone();
        this.model.collection = this.sourceModel.collection.clone();

        this.stopListening(previousModel, 'change');
        this.stopListening(previousModel, 'sync');

        this.listenTo(this.model, 'change', () => {
            this.sourceModel.set(this.model.getClonedAttributes());
        });

        this.listenTo(this.model, 'sync', () => {
            this.controlRecordButtonsVisibility();

            this.trigger('model-sync');
        });

        this.createRecordView(() => {
            this.reRender()
                .then(() => {
                    this.model.fetch();
                })
        });

        this.controlNavigationButtons();
        this.trigger('switch-model', this.model, previousModel);
    }

    actionPrevious() {
        if (!this.model.collection) {
            return;
        }

        if (!(this.indexOfRecord > 0)) {
            return;
        }

        let indexOfRecord = this.indexOfRecord - 1;

        this.switchToModelByIndex(indexOfRecord);
    }

    actionNext() {
        if (!this.model.collection) {
            return;
        }

        if (!(this.indexOfRecord < this.model.collection.total - 1) && this.model.collection.total >= 0) {
            return;
        }

        if (this.model.collection.total === -2 && this.indexOfRecord >= this.model.collection.length - 1) {
            return;
        }

        let collection = this.model.collection;

        let indexOfRecord = this.indexOfRecord + 1;

        if (indexOfRecord <= collection.length - 1) {
            this.switchToModelByIndex(indexOfRecord);

            return;
        }

        collection
            .fetch({
                more: true,
                remove: false,
            })
            .then(() => {
                this.switchToModelByIndex(indexOfRecord);
            });
    }

    /**
     * @return {Promise}
     */
    actionEdit() {
        if (this.options.quickEditDisabled) {
            let options = {
                id: this.id,
                model: this.model,
                returnUrl: this.getRouter().getCurrentUrl(),
            };

            if (this.options.rootUrl) {
                options.rootUrl = this.options.rootUrl;
            }

            this.getRouter().navigate('#' + this.scope + '/edit/' + this.id, {trigger: false});
            this.getRouter().dispatch(this.scope, 'edit', options);

            return Promise.reject();
        }

        let viewName = this.getMetadata().get(['clientDefs', this.scope, 'modalViews', 'edit']) ||
            'views/modals/edit';

        Espo.Ui.notify(' ... ');

        return new Promise(resolve => {
            this.createView('quickEdit', viewName, {
                scope: this.scope,
                entityType: this.model.entityType,
                id: this.id,
                fullFormDisabled: this.fullFormDisabled
            }, view => {
                this.listenToOnce(view, 'remove', () => {
                    this.dialog.show();
                });

                this.listenToOnce(view, 'leave', () => {
                    this.remove();
                });

                this.listenTo(view, 'after:save', (model, o) => {
                    this.model.set(model.getClonedAttributes());

                    this.trigger('after:save', model, o);
                    this.controlRecordButtonsVisibility();

                    this.trigger('model-sync');
                });

                view.render()
                    .then(() => {
                        Espo.Ui.notify(false);
                        this.dialog.hide();

                        resolve(view);
                    });
            });
        });
    }

    actionRemove() {
        let model = this.getRecordView().model;

        this.confirm(this.translate('removeRecordConfirmation', 'messages'), () => {
            let $buttons = this.dialog.$el.find('.modal-footer button');

            $buttons.addClass('disabled').attr('disabled', 'disabled');

            model.destroy()
                .then(() => {
                    this.trigger('after:destroy', model);
                    this.dialog.close();
                })
                .catch(() => {
                    $buttons.removeClass('disabled').removeAttr('disabled');
                });
        });
    }

    actionFullForm() {
        let url;
        let router = this.getRouter();

        let scope = this.getScope();

        url = '#' + scope + '/view/' + this.id;

        let attributes = this.getRecordView().fetch();
        let model = this.getRecordView().model;

        attributes = _.extend(attributes, model.getClonedAttributes());

        let options = {
            attributes: attributes,
            returnUrl: Backbone.history.fragment,
            model: this.sourceModel || this.model,
            id: this.id,
        };

        if (this.options.rootUrl) {
            options.rootUrl = this.options.rootUrl;
        }

        setTimeout(() => {
            router.dispatch(scope, 'view', options);
            router.navigate(url, {trigger: false});
        }, 10);

        this.trigger('leave');
        this.dialog.close();
    }

    actionDuplicate() {
        Espo.Ui.notify(' ... ');

        Espo.Ajax
            .postRequest(this.scope + '/action/getDuplicateAttributes', {id: this.model.id})
            .then(attributes => {
                Espo.Ui.notify(false);

                let url = '#' + this.scope + '/create';

                this.getRouter().dispatch(this.scope, 'create', {
                    attributes: attributes,
                    returnUrl: this.getRouter().getCurrentUrl(),
                    options: {
                        duplicateSourceId: this.model.id,
                        returnAfterCreate: true,
                    },
                });

                this.getRouter().navigate(url, {trigger: false});
            });
    }

    /**
     * @protected
     * @param {JQueryKeyEventObject} e
     */
    handleShortcutKeyControlArrowLeft(e) {
        if (!this.model.collection) {
            return;
        }

        if (this.buttonList.findIndex(item => item.name === 'previous' && !item.disabled) === -1) {
            return;
        }

        if (e.target.tagName === 'TEXTAREA' || e.target.tagName === 'INPUT') {
            return;
        }

        e.preventDefault();
        e.stopPropagation();

        this.actionPrevious();
    }

    /**
     * @protected
     * @param {JQueryKeyEventObject} e
     */
    handleShortcutKeyControlArrowRight(e) {
        if (!this.model.collection) {
            return;
        }

        if (this.buttonList.findIndex(item => item.name === 'next' && !item.disabled) === -1) {
            return;
        }

        if (e.target.tagName === 'TEXTAREA' || e.target.tagName === 'INPUT') {
            return;
        }

        e.preventDefault();
        e.stopPropagation();

        this.actionNext();
    }
}

export default DetailModalView;
PK]��S���.views/modals/select-records-with-categories.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import SelectRecordsModal from 'views/modals/select-records';
import ListWithCategories from 'views/list-with-categories';

class SelectRecordsWithCategoriesModalView extends SelectRecordsModal {

    template = 'modals/select-records-with-categories'

    // Used in applyCategoryToCollection.
    // noinspection JSUnusedGlobalSymbols
    categoryField = 'category'
    // noinspection JSUnusedGlobalSymbols
    categoryFilterType = 'inCategory'
    categoryScope = ''
    isExpanded = true

    data() {
        return {
            ...super.data(),
            categoriesDisabled: this.categoriesDisabled,
        };
    }

    setup() {
        this.scope = this.entityType = this.options.scope || this.scope;
        this.categoryScope = this.categoryScope || this.scope + 'Category';

        this.categoriesDisabled = this.categoriesDisabled ||
           this.getMetadata().get(['scopes',  this.categoryScope, 'disabled']) ||
           !this.getAcl().checkScope(this.categoryScope);

        super.setup();
    }

    setupList() {
        if (!this.categoriesDisabled) {
            this.setupCategories();
        }

        super.setupList();
    }

    setupCategories() {
        this.getCollectionFactory().create(this.categoryScope, collection => {
            this.treeCollection = collection;

            collection.url = collection.entityType + '/action/listTree';
            collection.data.onlyNotEmpty = true;

            collection.fetch()
                .then(() => this.createCategoriesView());
        });
    }

    createCategoriesView() {
        this.createView('categories', 'views/record/list-tree', {
            collection: this.treeCollection,
            selector: '.categories-container',
            selectable: true,
            readOnly: true,
            showRoot: true,
            rootName: this.translate(this.scope, 'scopeNamesPlural'),
            buttonsDisabled: true,
            checkboxes: false,
            isExpanded: this.isExpanded,
        }, view => {
            if (this.isRendered()) {
                view.render();
            } else {
                this.listenToOnce(this, 'after:render', () => view.render());
            }

            this.listenTo(view, 'select', model => {
                this.currentCategoryId = null;
                this.currentCategoryName = '';

                if (model && model.id) {
                    this.currentCategoryId = model.id;
                    this.currentCategoryName = model.get('name');
                }

                this.applyCategoryToCollection();

                Espo.Ui.notify(' ... ');

                this.collection.fetch()
                    .then(() => Espo.Ui.notify(false));
            });
        });
    }

    applyCategoryToCollection() {
        ListWithCategories.prototype.applyCategoryToCollection.call(this);
    }

    // noinspection JSUnusedGlobalSymbols
    isCategoryMultiple() {
        ListWithCategories.prototype.isCategoryMultiple.call(this);
    }
}

// noinspection JSUnusedGlobalSymbols
export default SelectRecordsWithCategoriesModalView;
PK]���iRRviews/modals/change-password.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ModalView from 'views/modal';

class ChangePasswordModalView extends ModalView {

    template = 'modals/change-password'

    cssName = 'change-password'
    className = 'dialog dialog-record'

    setup() {
        this.buttonList = [
            {
                name: 'change',
                label: 'Change',
                style: 'danger',
            },
            {
                name: 'cancel',
                label: 'Cancel',
            },
        ];

        this.headerText = this.translate('Change Password', 'labels', 'User');

        const promise = this.getModelFactory().create('User', user => {
            this.model = user;

            this.createView('currentPassword', 'views/fields/password', {
                model: user,
                mode: 'edit',
                selector:  '.field[data-name="currentPassword"]',
                defs: {
                    name: 'currentPassword',
                    params: {
                        required: true,
                    },
                },
            });

            this.createView('password', 'views/user/fields/password', {
                model: user,
                mode: 'edit',
                selector: '.field[data-name="password"]',
                defs: {
                    name: 'password',
                    params: {
                        required: true,
                    },
                },
            });

            this.createView('passwordConfirm', 'views/fields/password', {
                model: user,
                mode: 'edit',
                selector: '.field[data-name="passwordConfirm"]',
                defs: {
                    name: 'passwordConfirm',
                    params: {
                        required: true,
                    },
                },
            });
        });

        this.wait(promise);
    }

    /**
     * @param {string} field
     * @return {module:views/fields/base}
     */
    getFieldView(field) {
        return this.getView(field);
    }

    // noinspection JSUnusedGlobalSymbols
    actionChange() {
        this.getFieldView('currentPassword').fetchToModel();
        this.getFieldView('password').fetchToModel();
        this.getFieldView('passwordConfirm').fetchToModel();

        let notValid =
            this.getFieldView('currentPassword').validate() ||
            this.getFieldView('password').validate() ||
            this.getFieldView('passwordConfirm').validate();

        if (notValid) {
            return;
        }

        this.$el.find('button[data-name="change"]').addClass('disabled');

        Espo.Ajax
            .putRequest('UserSecurity/password', {
                currentPassword: this.model.get('currentPassword'),
                password: this.model.get('password'),
            })
            .then(() => {
                Espo.Ui.success(this.translate('passwordChanged', 'messages', 'User'));

                this.trigger('changed');
                this.close();
            })
            .catch(() => {
                this.$el.find('button[data-name="change"]').removeClass('disabled');
            });
    }
}

export default ChangePasswordModalView;
PK]����views/modals/edit-dashboard.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ModalView from 'views/modal';
import Model from 'model';

class EditDashboardModalView extends ModalView {

    template = 'modals/edit-dashboard'

    className = 'dialog dialog-record'
    cssName = 'edit-dashboard'

    data() {
        return {
            hasLocked: this.hasLocked,
        };
    }

    events = {
        /** @this EditDashboardModalView */
        'click button.add': function (e) {
            let name = $(e.currentTarget).data('name');

            this.getParentDashboardView().addDashlet(name);
            this.close();
        },
    }

    shortcutKeys = {
        'Control+Enter': 'save',
    }

    /**
     * @return {module:views/dashboard}
     */
    getParentDashboardView() {
        return /** @type module:views/dashboard */this.getParentView();
    }

    setup() {
        this.buttonList = [
            {
                name: 'save',
                label: this.options.fromDashboard ? 'Save': 'Apply',
                style: 'primary',
                title: 'Ctrl+Enter',
            },
            {
                name: 'cancel',
                label: 'Cancel',
                title: 'Esc',
            }
        ];

        let dashboardLayout = this.options.dashboardLayout || [];

        let dashboardTabList = [];

        dashboardLayout.forEach(item => {
            if (item.name) {
                dashboardTabList.push(item.name);
            }
        });

        let model = this.model = new Model({}, {entityType: 'Preferences'});

        model.set('dashboardTabList', dashboardTabList);

        this.hasLocked = 'dashboardLocked' in this.options;

        if (this.hasLocked) {
            model.set('dashboardLocked', this.options.dashboardLocked || false);
        }

        this.createView('dashboardTabList', 'views/preferences/fields/dashboard-tab-list', {
            selector: '.field[data-name="dashboardTabList"]',
            defs: {
                name: 'dashboardTabList',
                params: {
                    required: true,
                    noEmptyString: true,
                }
            },
            mode: 'edit',
            model: model,
        });

        if (this.hasLocked) {
            this.createView('dashboardLocked', 'views/fields/bool', {
                selector: '.field[data-name="dashboardLocked"]',
                mode: 'edit',
                model: model,
                defs: {
                    name: 'dashboardLocked',
                },
            })
        }

        this.headerText = this.translate('Edit Dashboard');

        this.dashboardLayout = this.options.dashboardLayout;
    }

    /**
     * @param {string} field
     * @return {module:views/fields/base}
     */
    getFieldView(field) {
        return this.getView(field);
    }

    actionSave() {
        const dashboardTabListView = this.getFieldView('dashboardTabList');

        dashboardTabListView.fetchToModel();

        if (this.hasLocked) {
            const dashboardLockedView = this.getFieldView('dashboardLocked');

            dashboardLockedView.fetchToModel();
        }

        if (dashboardTabListView.validate()) {
            return;
        }

        let attributes = {};

        attributes.dashboardTabList = this.model.get('dashboardTabList');

        if (this.hasLocked) {
            attributes.dashboardLocked = this.model.get('dashboardLocked');
        }

        let names = this.model.get('translatedOptions');

        let renameMap = {};

        for (let name in names) {
            if (name !== names[name]) {
                renameMap[name] = names[name];
            }
        }

        attributes.renameMap = renameMap;

        this.trigger('after:save', attributes);

        this.dialog.close();
    }
}

export default EditDashboardModalView;
PK]l�(%views/modals/resolve-save-conflict.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ModalView from 'views/modal';

class ResolveSaveConflictModalView extends ModalView {

    template = 'modals/resolve-save-conflict'

    backdrop = true

    resolutionList = [
        'current',
        'actual',
        'original',
    ]

    defaultResolution = 'current'

    data() {
        let dataList = [];

        this.fieldList.forEach(item => {
            let o = {
                field: item,
                viewKey: item + 'Field',
                resolution: this.defaultResolution,
            };

            dataList.push(o);
        });

        return {
            dataList: dataList,
            entityType: this.entityType,
            resolutionList: this.resolutionList,
        };
    }

    setup() {
        this.headerText = this.translate('Resolve Conflict');

        this.buttonList = [
            {
                name: 'apply',
                label: 'Apply',
                style: 'danger',
            },
            {
                name: 'cancel',
                label: 'Cancel',
            },
        ];

        this.entityType = this.model.entityType;

        this.originalModel = this.model;

        this.originalAttributes = Espo.Utils.cloneDeep(this.options.originalAttributes);
        this.currentAttributes = Espo.Utils.cloneDeep(this.options.currentAttributes);
        this.actualAttributes = Espo.Utils.cloneDeep(this.options.actualAttributes);

        let attributeList = this.options.attributeList;

        let fieldList = [];

        this.getFieldManager()
            .getEntityTypeFieldList(this.entityType)
            .forEach(field => {
                let fieldAttributeList = this.getFieldManager()
                    .getEntityTypeFieldAttributeList(this.entityType, field);

                let intersect = attributeList.filter(value => fieldAttributeList.includes(value));

                if (intersect.length) {
                    fieldList.push(field);
                }
            });

        this.fieldList = fieldList;

        this.wait(
            this.getModelFactory().create(this.entityType)
                .then(model => {
                    this.model = model;

                    this.fieldList.forEach(field => {
                        this.setResolution(field, this.defaultResolution);
                    });

                    this.fieldList.forEach(field => {
                        this.createField(field);
                    });
                })
        );
    }

    setResolution(field, resolution) {
        let attributeList = this.getFieldManager()
            .getEntityTypeFieldAttributeList(this.entityType, field);

        let values = {};

        let source = this.currentAttributes;

        if (resolution === 'actual') {
            source = this.actualAttributes;
        }
        else if (resolution === 'original') {
            source = this.originalAttributes;
        }

        for (let attribute of attributeList) {
            values[attribute] = source[attribute] || null;
        }

        this.model.set(values);
    }

    createField(field) {
        let type = this.model.getFieldType(field);

        let viewName =
            this.model.getFieldParam(field, 'view') ||
            this.getFieldManager().getViewName(type);

        this.createView(field + 'Field', viewName, {
            readOnly: true,
            model: this.model,
            name: field,
            selector: '[data-name="field"][data-field="' + field + '"]',
            mode: 'list',
        });
    }

    afterRender() {
        this.$el.find('[data-name="resolution"]').on('change', e => {
            let $el = $(e.currentTarget);

            let field = $el.attr('data-field');
            let resolution = $el.val();

            this.setResolution(field, resolution);
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionApply() {
        let attributes = this.model.attributes;

        this.originalModel.set(attributes);

        this.trigger('resolve');
        this.close();
    }
}

export default ResolveSaveConflictModalView;
PK]�hBviews/modals/image-preview.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ModalView from 'views/modal';

let Exif;

class ImagePreviewModalView extends ModalView {

    template = 'modals/image-preview'

    cssName = 'image-preview'
    size = ''
    backdrop = true

    transformClassList = [
        'transform-flip',
        'transform-rotate-180',
        'transform-flip-and-rotate-180',
        'transform-flip-and-rotate-270',
        'transform-rotate-90',
        'transform-flip-and-rotate-90',
        'transform-rotate-270',
    ]

    events = {
        /** @this ImagePreviewModalView */
        'keydown': function (e) {
            if (e.code === 'ArrowLeft') {
                this.switchToPrevious(true);

                return;
            }

            if (e.code === 'ArrowRight') {
                this.switchToNext(true);
            }
        },
    }

    data() {
        return {
            name: this.options.name,
            url: this.getImageUrl(),
            originalUrl: this.getOriginalImageUrl(),
            showOriginalLink: this.size,
        };
    }

    setup() {
        this.buttonList = [];
        this.headerHtml = '&nbsp;';

        this.navigationEnabled = (this.options.imageList && this.options.imageList.length > 1);

        this.imageList = this.options.imageList || [];

        this.once('remove', () => {
            $(window).off('resize.image-review');
        });

        this.wait(
            Espo.loader.requirePromise('lib!exif-js')
                .then(Lib => Exif = Lib)
        );
    }

    getImageUrl() {
        let url = this.getBasePath() + '?entryPoint=image&id=' + this.options.id;

        if (this.size) {
            url += '&size=' + this.size;
        }

        if (this.getUser().get('portalId')) {
            url += '&portalId=' + this.getUser().get('portalId');
        }

        return url;
    }

    getOriginalImageUrl() {
        let url = this.getBasePath() + '?entryPoint=image&id=' + this.options.id;

        if (this.getUser().get('portalId')) {
            url += '&portalId=' + this.getUser().get('portalId');
        }

        return url;
    }

    // noinspection JSUnusedGlobalSymbols
    onImageLoad() {}

    afterRender() {
        let $container = this.$el.find('.image-container');
        let $img = this.$img = this.$el.find('.image-container img');

        $img.on('load', () => {
            let imgEl = $img.get(0);

            Exif.getData(imgEl, () => {
                if ($img.css('image-orientation') === 'from-image') {
                    return;
                }

                let orientation = Exif.getTag(this, 'Orientation');

                switch (orientation) {
                    case 2:
                        $img.addClass('transform-flip');
                        break;
                    case 3:
                        $img.addClass('transform-rotate-180');
                        break;
                    case 4:
                        $img.addClass('transform-rotate-180');
                        $img.addClass('transform-flip');
                        break;
                    case 5:
                        $img.addClass('transform-rotate-270');
                        $img.addClass('transform-flip');
                        break;
                    case 6:
                        $img.addClass('transform-rotate-90');
                        break;
                    case 7:
                        $img.addClass('transform-rotate-90');
                        $img.addClass('transform-flip');
                        break;
                    case 8:
                        $img.addClass('transform-rotate-270');
                        break;
                }
            });

            if (imgEl.naturalWidth > imgEl.clientWidth) {
                this.$el.find('.original-link-container').removeClass('hidden');
            }
        });

        if (this.navigationEnabled) {
            $img.css('cursor', 'pointer');

            $img.click(() => {
                this.switchToNext();
            });
        }

        const manageSize = () => {
            let width = $container.width();

            $img.css('maxWidth', width);
        };

        $(window).off('resize.image-review');

        $(window).on('resize.image-review', () => {
            manageSize();
        });

        setTimeout(() => manageSize(), 100);
    }

    isMultiple() {
        return this.imageList.length > 1;
    }

    switchToPrevious(noLoop) {
        if (!this.isMultiple()) {
            return;
        }

        let index = -1;

        this.imageList.forEach((d, i) => {
            if (d.id === this.options.id) {
                index = i;
            }
        });

        if (noLoop && index === 0) {
            return;
        }

        this.transformClassList.forEach(item => {
            this.$img.removeClass(item);
        });

        index--;

        if (index < 0) {
            index = this.imageList.length - 1;
        }

        this.options.id = this.imageList[index].id;
        this.options.name = this.imageList[index].name;

        this.reRender();
    }

    switchToNext(noLoop) {
        if (!this.isMultiple()) {
            return;
        }

        let index = -1;

        this.imageList.forEach((d, i) => {
            if (d.id === this.options.id) {
                index = i;
            }
        });

        if (noLoop && index === this.imageList.length - 1) {
            return;
        }

        this.transformClassList.forEach(item => {
            this.$img.removeClass(item);
        });

        index++;

        if (index > this.imageList.length - 1) {
            index = 0;
        }

        this.options.id = this.imageList[index].id;
        this.options.name = this.imageList[index].name;

        this.reRender();
    }
}

export default ImagePreviewModalView;
PK]!r����views/modals/image-crop.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ModalView from 'views/modal';

class ImageCropModalView extends ModalView {

    template = 'modals/image-crop'

    cssName = 'image-crop'

    events = {
        /** @this ImageCropModalView */
        'click [data-action="zoomIn"]': function () {
            this.$img.cropper('zoom', 0.1);
        },
        /** @this ImageCropModalView */
        'click [data-action="zoomOut"]': function () {
            this.$img.cropper('zoom', -0.1);
        },
    }

    setup() {
        this.buttonList = [
            {
                name: 'crop',
                label: 'Submit',
                style: 'primary',
            },
            {
                name: 'cancel',
                label: 'Cancel',
            },
        ];

        this.wait(
            Espo.loader.requirePromise('lib!cropper')
        );

        this.on('remove', () => {
            if (this.$img.length) {
                this.$img.cropper('destroy');
                this.$img.parent().empty();
            }
        });
    }

    afterRender() {
        // noinspection RequiredAttributes,HtmlRequiredAltAttribute
        let $img = this.$img = $(`<img>`)
            .attr('src', this.options.contents)
            .addClass('hidden');

        this.$el.find('.image-container').append($img);

        setTimeout(() => {
            $img.cropper({
                aspectRatio: 1,
                movable: true,
                resizable: true,
                rotatable: false,
            });
        }, 50);
    }

    // noinspection JSUnusedGlobalSymbols
    actionCrop() {
        let dataUrl = this.$img.cropper('getDataURL', 'image/jpeg');

        this.trigger('crop', dataUrl);
        this.close();
    }
}

export default ImageCropModalView;
PK]�k�_ views/modals/auth2fa-required.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ModalView from 'views/modal';

class Auth2faRequiredModalView extends ModalView {

    noCloseButton = true
    escapeDisabled = true

    events = {
        'click [data-action="proceed"]': 'actionProceed',
        'click [data-action="logout"]': 'actionLogout',
    }

    // language=Handlebars
    templateContent = `
        <div class="complex-text">{{complexText viewObject.messageText}}</div>
        <div class="button-container btn-group" style="margin-top: 30px">
        <button class="btn btn-primary" data-action="proceed">{{translate 'Proceed'}}</button>
        <button class="btn btn-default" data-action="logout">{{translate 'Log Out'}}</button></div>
    `

    setup() {
        this.buttonList = [];

        this.headerText = this.translate('auth2FARequiredHeader', 'messages', 'User');
        // noinspection JSUnusedGlobalSymbols
        this.messageText = this.translate('auth2FARequired', 'messages', 'User');
    }

    actionProceed() {
        this.createView('dialog', 'views/user/modals/security', {
            userModel: this.getUser(),
        }, view => {
            view.render();

            this.listenToOnce(view, 'done', () => {
                this.clearView('dialog');
                this.close();
            });
        });
    }

    actionLogout() {
        this.getRouter().logout();
    }
}

export default Auth2faRequiredModalView;
PK]H�D>YY'views/modals/password-change-request.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ModalView from 'views/modal';

class PasswordChangeRequestModalView extends ModalView {

    template = 'modals/password-change-request'

    cssName = 'password-change-request'
    className = 'dialog dialog-centered'
    noFullHeight = true
    footerAtTheTop = false

    setup() {
        this.buttonList = [
            {
                name: 'submit',
                label: 'Submit',
                style: 'danger',
                className: 'btn-s-wide',
            },
            {
                name: 'cancel',
                label: 'Close',
                pullLeft: true,
                className: 'btn-s-wide',
            }
        ];

        this.headerText = this.translate('Password Change Request', 'labels', 'User');

        this.once('close remove', () => {
            if (this.$userName) {
                this.$userName.popover('destroy');
            }

            if (this.$emailAddress) {
                this.$emailAddress.popover('destroy');
            }
        });
    }

    afterRender() {
        this.$userName = this.$el.find('input[name="username"]');
        this.$emailAddress = this.$el.find('input[name="emailAddress"]');
    }

    // noinspection JSUnusedGlobalSymbols
    actionSubmit() {
        let $userName = this.$userName;
        let $emailAddress = this.$emailAddress;

        let userName = $userName.val();
        let emailAddress = $emailAddress.val();

        let isValid = true;

        if (userName === '') {
            isValid = false;

            var message = this.getLanguage().translate('userCantBeEmpty', 'messages', 'User');

            this.isPopoverUserNameDestroyed = false;

            $userName.popover({
                container: 'body',
                placement: 'bottom',
                content: message,
                trigger: 'manual',
            }).popover('show');

            let $cellUserName = $userName.closest('.form-group');

            $cellUserName.addClass('has-error');

            $userName.one('mousedown click', () => {
                $cellUserName.removeClass('has-error');

                if (this.isPopoverUserNameDestroyed) {
                    return;
                }

                $userName.popover('destroy');
                this.isPopoverUserNameDestroyed = true;
            });
        }

        if (emailAddress === '') {
            isValid = false;

            let message = this.getLanguage().translate('emailAddressCantBeEmpty', 'messages', 'User');

            this.isPopoverEmailAddressDestroyed = false;

            $emailAddress.popover({
                container: 'body',
                placement: 'bottom',
                content: message,
                trigger: 'manual',
            }).popover('show');

            let $cellEmailAddress = $emailAddress.closest('.form-group');

            $cellEmailAddress.addClass('has-error');

            $emailAddress.one('mousedown click', () => {
                $cellEmailAddress.removeClass('has-error');

                if (this.isPopoverEmailAddressDestroyed) {
                    return;
                }

                $emailAddress.popover('destroy');

                this.isPopoverEmailAddressDestroyed = true;
            });
        }

        if (!isValid) {
            return;
        }

        let $submit = this.$el.find('button[data-name="submit"]');

        $submit.addClass('disabled');

        Espo.Ui.notify(this.translate('pleaseWait', 'messages'));

        Espo.Ajax
            .postRequest('User/passwordChangeRequest', {
                userName: userName,
                emailAddress: emailAddress,
                url: this.options.url,
            })
            .then(() => {
                Espo.Ui.notify(false);

                let msg = this.translate('uniqueLinkHasBeenSent', 'messages', 'User');

                msg += ' ' + this.translate('passwordRecoverySentIfMatched', 'messages', 'User');

                this.$el.find('.cell-userName').addClass('hidden');
                this.$el.find('.cell-emailAddress').addClass('hidden');

                $submit.addClass('hidden');

                this.$el.find('.msg-box').removeClass('hidden');
                this.$el.find('.msg-box').html('<span class="text-success">' + msg + '</span>');
            })
            .catch(xhr => {
                if (xhr.status === 404) {
                    Espo.Ui.error(this.translate('userNameEmailAddressNotFound', 'messages', 'User'));

                    xhr.errorIsHandled = true;
                }

                if (xhr.status === 403 && xhr.getResponseHeader('X-Status-Reason') === 'Already-Sent') {
                    Espo.Ui.error(this.translate('forbidden', 'messages', 'User'), true);

                    xhr.errorIsHandled = true;
                }

                $submit.removeClass('disabled');
            });
    }
}

export default PasswordChangeRequestModalView;
PK]��xss!views/scheduled-job/fields/job.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/scheduled-job/fields/job', ['views/fields/enum'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            if (this.isEditMode() || this.isDetailMode()) {
                this.wait(true);

                Espo.Ajax
                    .getRequest('Admin/jobs')
                    .then(data => {
                        this.params.options = data.filter(item => {
                            return !this.getMetadata().get(['entityDefs', 'ScheduledJob', 'jobs', item, 'isSystem']);
                        });

                        this.params.options.unshift('');

                        this.wait(false);
                    });
            }

            if (this.model.isNew()) {
                this.on('change', () => {
                    var job = this.model.get('job');

                    if (job) {
                        var label = this.getLanguage().translateOption(job, 'job', 'ScheduledJob');
                        var scheduling = this.getMetadata().get('entityDefs.ScheduledJob.jobSchedulingMap.' + job) ||
                            '*/10 * * * *';

                        this.model.set('name', label);
                        this.model.set('scheduling', scheduling);

                        return;
                    }

                    this.model.set('name', '');
                    this.model.set('scheduling', '');
                });
            }
        },
    });
});
PK]a��oo(views/scheduled-job/fields/scheduling.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/scheduled-job/fields/scheduling', ['views/fields/varchar'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            if (this.isEditMode() || this.isDetailMode()) {
                this.wait(
                    Espo.loader.requirePromise('lib!cronstrue')
                        .then(Cronstrue => {
                            this.Cronstrue = Cronstrue;

                            this.listenTo(this.model, 'change:' + this.name, () => this.showText());
                        })
                );
            }
        },

        afterRender: function () {
            Dep.prototype.afterRender.call(this);

            if (this.isEditMode() || this.isDetailMode()) {
                let $text = this.$text = $('<div class="small text-success"/>');

                this.$el.append($text);
                this.showText();
            }
        },

        showText: function () {
            if (!this.$text || !this.$text.length) {
                return;
            }

            if (!this.Cronstrue) {
                return;
            }

            var exp = this.model.get(this.name);

            if (!exp) {
                this.$text.text('');

                return;
            }

            if (exp === '* * * * *') {
                this.$text.text(this.translate('As often as possible', 'labels', 'ScheduledJob'));

                return;
            }

            var locale = 'en';
            var localeList = Object.keys(this.Cronstrue.default.locales);
            var language = this.getLanguage().name;

            if (~localeList.indexOf(language)) {
                locale = language;
            }
            else if (~localeList.indexOf(language.split('_')[0])) {
                locale = language.split('_')[0];
            }

            try {
                var text = this.Cronstrue.toString(exp, {
                    use24HourTimeFormat: !this.getDateTime().hasMeridian(),
                    locale: locale,
                });

            }
            catch (e) {
                text = this.translate('Not valid');
            }

            this.$text.text(text);
        },
    });
});
PK]�c�)<<(views/scheduled-job/record/panels/log.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/scheduled-job/record/panels/log', ['views/record/panels/relationship'], function (Dep) {

    return Dep.extend({

        setupListLayout: function () {
            var jobWithTargetList = this.getMetadata().get(['clientDefs', 'ScheduledJob', 'jobWithTargetList']) || [];

            if (~jobWithTargetList.indexOf(this.model.get('job'))) {
                this.listLayoutName = 'listSmallWithTarget'
            }
        },
    });
});
PK]�޽�$views/scheduled-job/record/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/scheduled-job/record/detail', ['views/record/detail'], function (Dep) {

    return Dep.extend({

        duplicateAction: false,
    });
});
PK]��d�cc"views/scheduled-job/record/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/scheduled-job/record/list', ['views/record/list'], function (Dep) {

    return Dep.extend({

    	quickDetailDisabled: true,

        quickEditDisabled: true,

        massActionList: ['remove', 'massUpdate'],

    });
});
PK]h���
�
views/scheduled-job/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/scheduled-job/list', ['views/list'], function (Dep) {

    return Dep.extend({

        searchPanel: false,

        setup: function () {
            Dep.prototype.setup.call(this);

            this.menu.buttons.push({
                link: '#Admin/jobs',
                text: this.translate('Jobs', 'labels', 'Admin'),
            });

            this.createView('search', 'views/base', {
                fullSelector: '#main > .search-container',
                template: 'scheduled-job/cronjob',
            });
        },

        afterRender: function () {
            Dep.prototype.afterRender.call(this);

            Espo.Ajax
                .getRequest('Admin/action/cronMessage')
                .then(data => {
                    this.$el.find('.cronjob .message').html(data.message);
                    this.$el.find('.cronjob .command').html('<strong>' + data.command + '</strong>');
                });
        },

        getHeader: function () {
            return this.buildHeaderHtml([
                $('<a>')
                    .attr('href', '#Admin')
                    .text(this.translate('Administration', 'labels', 'Admin')),
                this.getLanguage().translate(this.scope, 'scopeNamesPlural')
            ]);
        },
    });
});
PK]MQ|^��
views/home.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';

class HomeView extends View {

    template = 'home'

    setup() {
        let viewName = this.getMetadata().get(['clientDefs', 'Home', 'view']) ||
            'views/dashboard';

        this.createView('content', viewName, {selector: '> .home-content'});
    }
}

export default HomeView;
PK]msk�.views/authentication-provider/fields/method.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of tтhe "EspoCRM" word.
 ************************************************************************/

define('views/authentication-provider/fields/method', ['views/fields/enum'], function (Dep) {

    return Dep.extend({

        setupOptions: function () {
            /** @var {Object.<string, Object.<string, *>>} defs */
            let defs = this.getMetadata().get(['authenticationMethods']) || {};

            let options = Object.keys(defs)
                .filter(item => {
                    /** @var {Object.<string, *>} */
                    let data = defs[item].provider || {};

                    return data.isAvailable;
                });

            options.unshift('');

            this.params.options = options;
        },
    });
});
PK]�wz��,views/authentication-provider/record/edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/authentication-provider/record/edit', ['views/record/edit', 'helpers/misc/authentication-provider'],
function (Dep, Helper) {

    return Dep.extend({

        saveAndNewAction: false,

        /**
         * @private
         * @type {module:helpers/misc/authentication-provider}
         */
        helper: null,

        setup: function () {
            this.helper = new Helper(this);

            Dep.prototype.setup.call(this);
        },

        setupBeforeFinal: function () {
            this.dynamicLogicDefs = this.helper.setupMethods();

            Dep.prototype.setupBeforeFinal.call(this);

            this.helper.setupPanelsVisibility(() => {
                this.processDynamicLogic();
            });
        },

        modifyDetailLayout: function (layout) {
            this.helper.modifyDetailLayout(layout);
        },
    });
});
PK]�
d��.views/authentication-provider/record/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/authentication-provider/record/detail', ['views/record/detail', 'helpers/misc/authentication-provider'],
function (Dep, Helper) {

    return Dep.extend({

        editModeDisabled: true,

        /**
         * @private
         * @type {module:helpers/misc/authentication-provider.Class}
         */
        helper: null,

        setup: function () {
            this.helper = new Helper(this);

            Dep.prototype.setup.call(this);
        },

        setupBeforeFinal: function () {
            this.dynamicLogicDefs = this.helper.setupMethods();

            Dep.prototype.setupBeforeFinal.call(this);

            this.helper.setupPanelsVisibility(() => {
                this.processDynamicLogic();
            });
        },

        modifyDetailLayout: function (layout) {
            this.helper.modifyDetailLayout(layout);
        },
    });
});
PK]e�]]
views/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module module:views/list */

import MainView from 'views/main';
import SearchManager from 'search-manager';

/**
 * A list view.
 */
class ListView extends MainView {

    /** @inheritDoc */
    template = 'list'

    /** @inheritDoc */
    name = 'List'

    /** @inheritDoc */
    optionsToPass = []

    /**
     * A header view name.
     *
     * @type {string}
     * @protected
     */
    headerView = 'views/header'

    /**
     * A search view name.
     *
     * @type {string}
     * @protected
     */
    searchView = 'views/record/search'

    /**
     * A record/list view name.
     *
     * @type {string}
     * @protected
     */
    recordView = 'views/record/list'

    /**
     * A record/kanban view name.
     *
     * @type {string}
     * @protected
     */
    recordKanbanView = 'views/record/kanban'

    /**
     * Has a search panel.
     *
     * @type {boolean}
     * @protected
     */
    searchPanel = true

    /**
     * @type {module:search-manager}
     * @protected
     */
    searchManager = null

    /**
     * Has a create button.
     *
     * @type {boolean}
     * @protected
     */
    createButton = true

    /**
     * To use a modal dialog when creating a record.
     *
     * @type {boolean}
     * @protected
     */
    quickCreate = false

    /**
     * After create a view will be stored, so it can be re-used after.
     * Useful to avoid re-rendering when come back the list view.
     *
     * @type {boolean}
     */
    storeViewAfterCreate = false

    /**
     * After update a view will be stored, so it can be re-used after.
     * Useful to avoid re-rendering when come back the list view.
     *
     * @type {boolean}
     */
    storeViewAfterUpdate = true

    /**
     * Use a current URL as a root URL when open a record. To be able to return to the same URL.
     */
    keepCurrentRootUrl = false

    /**
     * A view mode. 'list', 'kanban`.
     *
     * @type {string}
     */
    viewMode = ''

    /**
     * An available view mode list.
     *
     * @type {string[]|null}
     */
    viewModeList = null

    /**
     * A default view mode.
     *
     * @type {string}
     */
    defaultViewMode = 'list'

    /** @const */
    MODE_LIST = 'list'
    /** @const */
    MODE_KANBAN = 'kanban'

    /** @inheritDoc */
    shortcutKeys = {
        /** @this ListView */
        'Control+Space': function (e) {
            this.handleShortcutKeyCtrlSpace(e);
        },
        /** @this ListView */
        'Control+Slash': function (e) {
            this.handleShortcutKeyCtrlSlash(e);
        },
        /** @this ListView */
        'Control+Comma': function (e) {
            this.handleShortcutKeyCtrlComma(e);
        },
        /** @this ListView */
        'Control+Period': function (e) {
            this.handleShortcutKeyCtrlPeriod(e);
        },
    }

    /** @inheritDoc */
    setup() {
        this.collection.maxSize = this.getConfig().get('recordsPerPage') || this.collection.maxSize;

        this.collectionUrl = this.collection.url;
        this.collectionMaxSize = this.collection.maxSize;

        this.setupModes();
        this.setViewMode(this.viewMode);

        if (this.getMetadata().get(['clientDefs', this.scope, 'searchPanelDisabled'])) {
            this.searchPanel = false;
        }

        if (this.getUser().isPortal()) {
            if (this.getMetadata().get(['clientDefs', this.scope, 'searchPanelInPortalDisabled'])) {
                this.searchPanel = false;
            }
        }

        if (this.getMetadata().get(['clientDefs', this.scope, 'createDisabled'])) {
            this.createButton = false;
        }

        this.entityType = this.collection.entityType;

        this.headerView = this.options.headerView || this.headerView;
        this.recordView = this.options.recordView || this.recordView;
        this.searchView = this.options.searchView || this.searchView;

        this.setupHeader();

        this.defaultOrderBy = this.defaultOrderBy || this.collection.orderBy;
        this.defaultOrder = this.defaultOrder || this.collection.order;

        this.collection.setOrder(this.defaultOrderBy, this.defaultOrder, true);

        if (this.searchPanel) {
            this.setupSearchManager();
        }

        this.setupSorting();

        if (this.searchPanel) {
            this.setupSearchPanel();
        }

        if (this.createButton) {
            this.setupCreateButton();
        }

        if (this.options.params && this.options.params.fromAdmin) {
            this.keepCurrentRootUrl = true;
        }
    }

    setupFinal() {
        super.setupFinal();

        this.wait(
            this.getHelper().processSetupHandlers(this, 'list')
        );
    }

    /**
     * Set up modes.
     */
    setupModes() {
        this.defaultViewMode = this.options.defaultViewMode ||
            this.getMetadata().get(['clientDefs', this.scope, 'listDefaultViewMode']) ||
            this.defaultViewMode;

        this.viewMode = this.viewMode || this.defaultViewMode;

        let viewModeList = this.options.viewModeList ||
            this.viewModeList ||
            this.getMetadata().get(['clientDefs', this.scope, 'listViewModeList']);

        if (viewModeList) {
            this.viewModeList = viewModeList;
        }
        else {
            this.viewModeList = [this.MODE_LIST];

            if (this.getMetadata().get(['clientDefs', this.scope, 'kanbanViewMode'])) {
                if (!~this.viewModeList.indexOf(this.MODE_KANBAN)) {
                    this.viewModeList.push(this.MODE_KANBAN);
                }
            }
        }

        if (this.viewModeList.length > 1) {
            let viewMode = null;

            let modeKey = 'listViewMode' + this.scope;

            if (this.getStorage().has('state', modeKey)) {
                let storedViewMode = this.getStorage().get('state', modeKey);

                if (storedViewMode && this.viewModeList.includes(storedViewMode)) {
                    viewMode = storedViewMode;
                }
            }

            if (!viewMode) {
                viewMode = this.defaultViewMode;
            }

            this.viewMode = /** @type {string} */viewMode;
        }
    }

    /**
     * Set up a header.
     */
    setupHeader() {
        this.createView('header', this.headerView, {
            collection: this.collection,
            fullSelector: '#main > .page-header',
            scope: this.scope,
            isXsSingleRow: true,
        });
    }

    /**
     * Set up a create button.
     */
    setupCreateButton() {
        if (this.quickCreate) {
            this.menu.buttons.unshift({
                action: 'quickCreate',
                iconHtml: '<span class="fas fa-plus fa-sm"></span>',
                text: this.translate('Create ' +  this.scope, 'labels', this.scope),
                style: 'default',
                acl: 'create',
                aclScope: this.entityType || this.scope,
                title: 'Ctrl+Space',
            });

            return;
        }

        this.menu.buttons.unshift({
            link: '#' + this.scope + '/create',
            action: 'create',
            iconHtml: '<span class="fas fa-plus fa-sm"></span>',
            text: this.translate('Create ' +  this.scope,  'labels', this.scope),
            style: 'default',
            acl: 'create',
            aclScope: this.entityType || this.scope,
            title: 'Ctrl+Space',
        });
    }

    /**
     * Set up a search panel.
     *
     * @protected
     */
    setupSearchPanel() {
        this.createSearchView();
    }

    /**
     * Create a search view.
     *
     * @return {Promise<module:view>}
     * @protected
     */
    createSearchView() {
        return this.createView('search', this.searchView, {
            collection: this.collection,
            fullSelector: '#main > .search-container',
            searchManager: this.searchManager,
            scope: this.scope,
            viewMode: this.viewMode,
            viewModeList: this.viewModeList,
            isWide: true,
        }, view => {
            this.listenTo(view, 'reset', () => this.resetSorting());

            if (this.viewModeList.length > 1) {
                this.listenTo(view, 'change-view-mode', mode => this.switchViewMode(mode));
            }
        });
    }

    /**
     * Switch a view mode.
     *
     * @param {string} mode
     */
    switchViewMode(mode) {
        this.clearView('list');
        this.collection.isFetched = false;
        this.collection.reset();
        this.applyStoredSorting();
        this.setViewMode(mode, true);
        this.loadList();
    }

    /**
     * Set a view mode.
     *
     * @param {string} mode A mode.
     * @param {boolean} [toStore=false] To preserve a mode being set.
     */
    setViewMode(mode, toStore) {
        this.viewMode = mode;

        this.collection.url = this.collectionUrl;
        this.collection.maxSize = this.collectionMaxSize;

        if (toStore) {
            let modeKey = 'listViewMode' + this.scope;

            this.getStorage().set('state', modeKey, mode);
        }

        if (this.searchView && this.getView('search')) {
            this.getSearchView().setViewMode(mode);
        }

        if (this.viewMode === this.MODE_KANBAN) {
            this.setViewModeKanban();

            return;
        }

        let methodName = 'setViewMode' + Espo.Utils.upperCaseFirst(this.viewMode);

        if (this[methodName]) {
            this[methodName]();
        }
    }

    /**
     * Called when the kanban mode is set.
     */
    setViewModeKanban() {
        this.collection.url = 'Kanban/' + this.scope;
        this.collection.maxSize = this.getConfig().get('recordsPerPageKanban');
        this.collection.resetOrderToDefault();
    }

    /**
     * Reset sorting in a storage.
     */
    resetSorting() {
        this.getStorage().clear('listSorting', this.collection.entityType);
    }

    /**
     * Get default search data.
     *
     * @returns {Object}
     */
    getSearchDefaultData() {
        return this.getMetadata().get('clientDefs.' + this.scope + '.defaultFilterData');
    }

    /**
     * Set up a search manager.
     */
    setupSearchManager() {
        let collection = this.collection;

        const searchManager = new SearchManager(
            collection,
            'list',
            this.getStorage(),
            this.getDateTime(),
            this.getSearchDefaultData()
        );

        searchManager.scope = this.scope;
        searchManager.loadStored();

        collection.where = searchManager.getWhere();

        this.searchManager = searchManager;
    }

    /**
     * Set up sorting.
     */
    setupSorting() {
        if (!this.searchPanel) {
            return;
        }

        this.applyStoredSorting();
    }

    /**
     * Apply stored sorting.
     */
    applyStoredSorting() {
        let sortingParams = this.getStorage().get('listSorting', this.collection.entityType) || {};

        if ('orderBy' in sortingParams) {
            this.collection.orderBy = sortingParams.orderBy;
        }

        if ('order' in sortingParams) {
            this.collection.order = sortingParams.order;
        }
    }

    /**
     * @protected
     * @return {module:views/record/search}
     */
    getSearchView() {
        return this.getView('search');
    }

    /**
     * @protected
     * @return {module:view}
     */
    getRecordView() {
        return this.getView('list');
    }

    /**
     * Get a record view name.
     *
     * @returns {string}
     */
    getRecordViewName() {
        let viewName = this.getMetadata().get(['clientDefs', this.scope, 'recordViews', this.viewMode]);

        if (viewName) {
            return viewName;
        }

        if (this.viewMode === this.MODE_LIST) {
            return this.recordView;
        }

        if (this.viewMode === this.MODE_KANBAN) {
            return this.recordKanbanView;
        }

        let propertyName = 'record' + Espo.Utils.upperCaseFirst(this.viewMode) + 'View';

        viewName = this[propertyName];

        if (!viewName) {
            throw new Error("No record view.");
        }

        return viewName;
    }

    /** @inheritDoc */
    cancelRender() {
        if (this.hasView('list')) {
            this.getRecordView();

            if (this.getRecordView().isBeingRendered()) {
                this.getRecordView().cancelRender();
            }
        }

        super.cancelRender();
    }

    /**
     * @inheritDoc
     */
    afterRender() {
        Espo.Ui.notify(false);

        if (!this.hasView('list')) {
            this.loadList();
        }

        // noinspection JSUnresolvedReference
        this.$el.get(0).focus({preventScroll: true});
    }

    /**
     * Load a record list view.
     */
    loadList() {
        if ('isFetched' in this.collection && this.collection.isFetched) {
            this.createListRecordView(false);

            return;
        }

        Espo.Ui.notify(' ... ');

        this.createListRecordView(true);
    }

    /**
     * Prepare record view options. Options can be modified in an extended method.
     *
     * @param {Object} options Options
     */
    prepareRecordViewOptions(options) {}

    /**
     * Create a record list view.
     *
     * @param {boolean} [fetch=false] To fetch after creation.
     * @return {Promise<module:views/record/list>}
     */
    createListRecordView(fetch) {
        let o = {
            collection: this.collection,
            selector: '.list-container',
            scope: this.scope,
            skipBuildRows: true,
            shortcutKeysEnabled: true,
            forceDisplayTopBar: true,
        };

        if (this.getHelper().isXsScreen()) {
            o.type = 'listSmall';
        }

        this.optionsToPass.forEach(option => {
            o[option] = this.options[option];
        });

        if (this.keepCurrentRootUrl) {
            o.keepCurrentRootUrl = true;
        }

        if (
            this.getConfig().get('listPagination') ||
            this.getMetadata().get(['clientDefs', this.scope, 'listPagination'])
        ) {
            // @todo Remove in v8.1.
            console.warn(`'listPagination' parameter is deprecated and will be removed in the future.`);

            o.pagination = true;
        }

        this.prepareRecordViewOptions(o);

        let listViewName = this.getRecordViewName();

        return this.createView('list', listViewName, o, view => {
            if (!this.hasParentView()) {
                view.undelegateEvents();

                return;
            }

            this.listenToOnce(view, 'after:render', () => {
                if (!this.hasParentView()) {
                    view.undelegateEvents();

                    this.clearView('list');
                }
            });

            if (!fetch) {
                Espo.Ui.notify(false);
            }

            if (this.searchPanel) {
                this.listenTo(view, 'sort', obj => {
                    this.getStorage().set('listSorting', this.collection.entityType, obj);
                });
            }

            if (!fetch) {
                view.render();

                return;
            }

            view.getSelectAttributeList(selectAttributeList => {
                if (this.options.mediator && this.options.mediator.abort) {
                    return;
                }

                if (selectAttributeList) {
                    this.collection.data.select = selectAttributeList.join(',');
                }

                Espo.Ui.notify(' ... ');

                this.collection.fetch({main: true})
                    .then(() => Espo.Ui.notify(false));
            });
        });
    }

    /**
     * @inheritDoc
     */
    getHeader() {
        if (this.options.params && this.options.params.fromAdmin) {
            let $root = $('<a>')
                .attr('href', '#Admin')
                .text(this.translate('Administration', 'labels', 'Admin'));

            let $scope = $('<span>')
                .text(this.getLanguage().translate(this.scope, 'scopeNamesPlural'));

            return this.buildHeaderHtml([$root, $scope]);
        }

        let $root = $('<span>')
            .text(this.getLanguage().translate(this.scope, 'scopeNamesPlural'));

        let headerIconHtml = this.getHeaderIconHtml();

        if (headerIconHtml) {
            $root.prepend(headerIconHtml);
        }

        return this.buildHeaderHtml([$root]);
    }

    /**
     * @inheritDoc
     */
    updatePageTitle() {
        this.setPageTitle(this.getLanguage().translate(this.scope, 'scopeNamesPlural'));
    }

    /**
     * Create attributes for an entity being created.
     *
     * @return {Object}
     */
    getCreateAttributes() {}

    /**
     * Prepare return dispatch parameters to pass to a view when creating a record.
     * To pass some data to restore when returning to the list view.
     *
     * Example:
     * ```
     * params.options.categoryId = this.currentCategoryId;
     * params.options.categoryName = this.currentCategoryName;
     * ```
     *
     * @param {Object} params Parameters to be modified.
     */
    prepareCreateReturnDispatchParams(params) {}

    /**
     * Action `quickCreate`.
     *
     * @param {Object.<string,*>} [data]
     * @returns {Promise<module:views/modals/edit>}
     */
    actionQuickCreate(data) {
        data = data || {};

        let attributes = this.getCreateAttributes() || {};

        Espo.Ui.notify(' ... ');

        let viewName = this.getMetadata().get('clientDefs.' + this.scope + '.modalViews.edit') ||
            'views/modals/edit';

        let options = {
            scope: this.scope,
            attributes: attributes,
        };

        if (this.keepCurrentRootUrl) {
            options.rootUrl = this.getRouter().getCurrentUrl();
        }

        if (data.focusForCreate) {
            options.focusForCreate = true;
        }

        let returnDispatchParams = {
            controller: this.scope,
            action: null,
            options: {isReturn: true},
        };

        this.prepareCreateReturnDispatchParams(returnDispatchParams);

        options = {
            ...options,
            returnUrl: this.getRouter().getCurrentUrl(),
            returnDispatchParams: returnDispatchParams,
        };

        return this.createView('quickCreate', viewName, options, (view) => {
            view.render();
            view.notify(false);

            this.listenToOnce(view, 'after:save', () => {
                this.collection.fetch();
            });
        });
    }

    /**
     * Action 'create'.
     *
     * @param {Object.<string,*>} [data]
     */
    actionCreate(data) {
        data = data || {};

        let router = this.getRouter();

        let url = '#' + this.scope + '/create';
        let attributes = this.getCreateAttributes() || {};

        let options = {attributes: attributes};

        if (this.keepCurrentRootUrl) {
            options.rootUrl = this.getRouter().getCurrentUrl();
        }

        if (data.focusForCreate) {
            options.focusForCreate = true;
        }

        let returnDispatchParams = {
            controller: this.scope,
            action: null,
            options: {isReturn: true},
        };

        this.prepareCreateReturnDispatchParams(returnDispatchParams);

        options = {
            ...options,
            returnUrl: this.getRouter().getCurrentUrl(),
            returnDispatchParams: returnDispatchParams,
        };

        router.navigate(url, {trigger: false});
        router.dispatch(this.scope, 'create', options);
    }

    /**
     * Whether the view is actual to be reused.
     *
     * @returns {boolean}
     */
    isActualForReuse() {
        return 'isFetched' in this.collection && this.collection.isFetched;
    }

    /**
     * @protected
     * @param {JQueryKeyEventObject} e
     */
    handleShortcutKeyCtrlSpace(e) {
        if (!this.createButton) {
            return;
        }

        /*if (e.target.tagName === 'TEXTAREA' || e.target.tagName === 'INPUT') {
            return;
        }*/

        if (!this.getAcl().checkScope(this.scope, 'create')) {
            return;
        }

        e.preventDefault();
        e.stopPropagation();

        if (this.quickCreate) {
            this.actionQuickCreate({focusForCreate: true});

            return;
        }

        this.actionCreate({focusForCreate: true});
    }

    /**
     * @protected
     * @param {JQueryKeyEventObject} e
     */
    handleShortcutKeyCtrlSlash(e) {
        if (!this.searchPanel) {
            return;
        }

        let $search = this.$el.find('input.text-filter').first();

        if (!$search.length) {
            return;
        }

        e.preventDefault();
        e.stopPropagation();

        $search.focus();
    }

    // noinspection JSUnusedLocalSymbols
    /**
     * @protected
     * @param {JQueryKeyEventObject} e
     */
    handleShortcutKeyCtrlComma(e) {
        if (!this.getSearchView()) {
            return;
        }

        this.getSearchView().selectPreviousPreset();
    }

    // noinspection JSUnusedLocalSymbols
    /**
     * @protected
     * @param {JQueryKeyEventObject} e
     */
    handleShortcutKeyCtrlPeriod(e) {
        if (!this.getSearchView()) {
            return;
        }

        this.getSearchView().selectNextPreset();
    }
}

export default ListView;
PK]����KKviews/about.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import View from 'view';

class AboutView extends View {

    template = 'about'

    data() {
        return {
            version: this.getConfig().get('version'),
        };
    }
}

export default AboutView;
PK]�a����!views/settings/fields/language.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/settings/fields/language', ['views/fields/enum'], function (Dep) {

    return Dep.extend({

        setupOptions: function () {
            this.params.options = Espo.Utils.clone(this.getMetadata().get(['app', 'language', 'list']) || []);
            this.translatedOptions = Espo.Utils.clone(this.getLanguage().translate('language', 'options') || {});
        },
    });
});
PK](1ZW	W	4views/settings/fields/outbound-email-from-address.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import EmailAddressFieldView from 'views/fields/email-address';

class SettingsOutboundEmailFromAddressFieldView extends EmailAddressFieldView {

    useAutocompleteUrl = true

    getAutocompleteUrl(q) {
        return 'InboundEmail?searchParams=' + JSON.stringify({
            select: ['emailAddress'],
            maxSize: 7,
            where: [
                {
                    type: 'startsWith',
                    attribute: 'emailAddress',
                    value: q,
                },
                {
                    type: 'isTrue',
                    attribute: 'useSmtp',
                },
            ],
        });
    }

    transformAutocompleteResult(response) {
        const result = super.transformAutocompleteResult(response);

        result.suggestions.forEach(item => {
            item.value = item.attributes.emailAddress;
        });

        return result;
    }
}

export default SettingsOutboundEmailFromAddressFieldView;
PK]��چ�0views/settings/fields/auth-two-fa-method-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/settings/fields/auth-two-fa-method-list', ['views/fields/multi-enum'], function (Dep) {

    return Dep.extend({

        setupOptions: function () {
            this.params.options = [];

            let defs = this.getMetadata().get(['app', 'authentication2FAMethods']) || {};

            for (let method in defs) {
                if (defs[method].settings && defs[method].settings.isAvailable) {
                    this.params.options.push(method);
                }
            }
        },
    });
});
PK]ˊ��?views/settings/fields/stream-email-notifications-entity-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/settings/fields/stream-email-notifications-entity-list',
['views/fields/entity-type-list'], function (Dep) {

    return Dep.extend({

        setupOptions: function () {

            Dep.prototype.setupOptions.call(this);

            this.params.options = this.params.options.filter(function (scope) {
                if (this.getMetadata().get('scopes.' + scope + '.disabled')) return;
                if (!this.getMetadata().get('scopes.' + scope + '.object')) return;
                if (!this.getMetadata().get('scopes.' + scope + '.stream')) return;

                return true;
            }, this)
        },
    });
});
PK]��W,		>views/settings/fields/email-address-lookup-entity-type-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/settings/fields/email-address-lookup-entity-type-list',
['views/fields/entity-type-list'], function (Dep) {

    return Dep.extend({

        setupOptions: function () {
            Dep.prototype.setupOptions.call(this);

            this.params.options = this.params.options.filter(scope => {
                if (this.getMetadata().get(['scopes', scope, 'disabled'])) {
                    return;
                }

                if (!this.getMetadata().get(['scopes', scope, 'object'])) {
                    return;
                }

                if (~['User', 'Contact', 'Lead', 'Account'].indexOf(scope)) {
                    return true;
                }

                var type = this.getMetadata().get(['scopes', scope, 'type']);

                if (type === 'Company' || type === 'Person') {
                    return true;
                }
            })
        },
    });
});
PK]!�OO+views/settings/fields/thousand-separator.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/settings/fields/thousand-separator', ['views/fields/varchar'], function (Dep) {

    return Dep.extend({

        validations: ['required', 'thousandSeparator'],

        validateThousandSeparator: function () {
            if (this.model.get('thousandSeparator') === this.model.get('decimalMark')) {
                var msg = this.translate('thousandSeparatorEqualsDecimalMark', 'messages', 'Admin');

                this.showValidationMessage(msg);

                return true;
            }
        },

        fetch: function () {
            var data = {};
            var value = this.$element.val();

            data[this.name] = value || '';

            return data;
        },
    });
});
PK]A�4���#views/settings/fields/pdf-engine.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/settings/fields/pdf-engine', ['views/fields/enum'], function (Dep) {

    return Dep.extend({

        setupOptions: function () {
            this.params.options = Object.keys(this.getMetadata().get(['app', 'pdfEngines']));

            if (this.params.options.length === 0) {
                this.params.options = [''];
            }
        },
    });
});
PK]��Ϣ&views/settings/fields/currency-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/settings/fields/currency-list', ['views/fields/multi-enum'], function (Dep) {

    return Dep.extend({

        matchAnyWord: true,

        setupOptions: function () {
            this.params.options = this.getMetadata().get(['app', 'currency', 'list']) || [];
            this.translatedOptions = {};

            this.params.options.forEach(item => {
                var value = item

                var name = this.getLanguage().get('Currency', 'names', item);

                if (name) {
                    value += ' - ' + name;
                }

                this.translatedOptions[item] = value;
            });
        },
    });
});
PK]��]z��$views/settings/fields/time-format.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/settings/fields/time-format', ['views/fields/enum'], function (Dep) {

    return Dep.extend({

        setupOptions: function () {
            this.params.options = this.getMetadata().get(['app', 'dateTime', 'timeFormatList']) || [];
        },
    });
});
PK]#RT3�	�	(views/settings/fields/address-preview.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/settings/fields/address-preview', ['views/fields/address'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            var mainModel = this.model;
            var model = mainModel.clone();

            model.entityType = mainModel.entityType;
            model.name = mainModel.name;

            model.set({
                addressPreviewStreet: 'Street',
                addressPreviewPostalCode: 'PostalCode',
                addressPreviewCity: 'City',
                addressPreviewState: 'State',
                addressPreviewCountry: 'Country',
            });

            this.listenTo(mainModel, 'change:addressFormat', () => {
                model.set('addressFormat', mainModel.get('addressFormat'));

                this.reRender();
            });

            this.model = model;
        },

        getAddressFormat: function () {
            return this.model.get('addressFormat') || 1;
        },
    });
});
PK]�>Mff*views/settings/fields/oidc-redirect-uri.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/settings/fields/oidc-redirect-uri', ['views/fields/varchar'], function (Dep) {

    return Dep.extend({

        detailTemplateContent: `
            {{#if isNotEmpty}}
                <a
                    role="button"
                    data-action="copyToClipboard"
                    class="pull-right text-soft"
                    title="{{translate 'Copy to Clipboard'}}"
                ><span class="far fa-copy"></span></a>
                {{value}}
            {{else}}
                <span class="none-value">{{translate 'None'}}</span>
            {{/if}}
        `,

        portalCollection: null,

        data: function () {
            let isNotEmpty = this.model.entityType !== 'AuthenticationProvider' ||
                this.portalCollection;

            return {
                value: this.getValueForDisplay(),
                isNotEmpty: isNotEmpty,
            };
        },

        /**
         * @protected
         */
        copyToClipboard: function () {
            let value = this.getValueForDisplay();

            navigator.clipboard.writeText(value).then(() => {
                Espo.Ui.success(this.translate('Copied to clipboard'));
            });
        },

        getValueForDisplay: function () {
            if (this.model.entityType === 'AuthenticationProvider') {
                if (!this.portalCollection) {
                    return null;
                }

                return this.portalCollection.models
                    .map(model => {
                        let url = (model.get('url') || '').replace(/\/+$/, '');

                        return url + '/oauth-callback.php';
                    })
                    .join('\n');
            }

            let siteUrl = (this.getConfig().get('siteUrl') || '').replace(/\/+$/, '');

            return siteUrl + '/oauth-callback.php';
        },

        setup: function () {
            Dep.prototype.setup.call(this);

            if (this.model.entityType === 'AuthenticationProvider') {
                this.getCollectionFactory()
                    .create('Portal')
                    .then(collection => {
                        collection.data.select = ['url', 'isDefault'];

                        collection.fetch().then(() => {
                            this.portalCollection = collection;

                            this.reRender();
                        })
                    });
            }
        },
    });
});
PK]
rT��%views/settings/fields/sms-provider.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/settings/fields/sms-provider', ['views/fields/enum'], function (Dep) {

    return Dep.extend({

        fetchEmptyValueAsNull: true,

        setupOptions: function () {
            this.params.options = Object.keys(
                this.getMetadata().get(['app', 'smsProviders']) || {}
            );

            this.params.options.unshift('');
        },
    });
});
PK]f3�0
0
=views/settings/fields/assignment-notifications-entity-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/settings/fields/assignment-notifications-entity-list', ['views/fields/multi-enum'], function (Dep) {

    return Dep.extend({

        setup: function () {

            this.params.options = Object.keys(this.getMetadata().get('scopes'))
                .filter(scope => {
                    if (this.getMetadata().get('scopes.' + scope + '.disabled')) {
                        return;
                    }

                    if (
                        this.getMetadata().get(['scopes', scope, 'stream'])
                        &&
                        !this.getMetadata().get(['entityDefs', scope, 'fields', 'assignedUsers'])
                    ) {
                        return;
                    }

                    return this.getMetadata().get('scopes.' + scope + '.notifications') &&
                           this.getMetadata().get('scopes.' + scope + '.entity');
                })
                .sort((v1, v2) => {
                    return this.translate(v1, 'scopeNamesPlural')
                        .localeCompare(this.translate(v2, 'scopeNamesPlural'));
                });

            Dep.prototype.setup.call(this);
        },
    });
});
PK]�ٔ11'views/settings/fields/group-tab-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/settings/fields/group-tab-list', ['views/settings/fields/tab-list'], function (Dep) {

    return Dep.extend({

        noGroups: true,

        noDelimiters: true,
    });
});
PK]V���tt.views/settings/fields/authentication-method.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/settings/fields/authentication-method', ['views/fields/enum'], function (Dep) {

    return Dep.extend({

        setupOptions: function () {
            this.params.options = [];

            let defs = this.getMetadata().get(['authenticationMethods']) || {};

            for (let method in defs) {
                if (defs[method].settings && defs[method].settings.isAvailable) {
                    this.params.options.push(method);
                }
            }
        },
    });
});
PK]]l���*views/settings/fields/quick-create-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/settings/fields/quick-create-list', ['views/fields/array'], function (Dep) {

    return Dep.extend({

        setup: function () {

            this.params.options =  Object.keys(this.getMetadata().get('scopes'))
                .filter(scope => {
                    if (this.getMetadata().get('scopes.' + scope + '.disabled')) {
                        return;
                    }

                    return this.getMetadata().get('scopes.' + scope + '.entity') &&
                        this.getMetadata().get('scopes.' + scope + '.object');
                })
                .sort((v1, v2) => {
                    return this.translate(v1, 'scopeNamesPlural')
                        .localeCompare(this.translate(v2, 'scopeNamesPlural'));
                });

            Dep.prototype.setup.call(this);
        },
    });
});
PK]!��F�F)views/settings/fields/dashboard-layout.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/settings/fields/dashboard-layout', ['views/fields/base', 'lib!gridstack'], function (Dep, GridStack) {

    return Dep.extend({

        detailTemplate: 'settings/fields/dashboard-layout/detail',
        editTemplate: 'settings/fields/dashboard-layout/edit',

        validationElementSelector: 'button[data-action="addDashlet"]',

        WIDTH_MULTIPLIER: 3,

        events: {
            'click button[data-action="selectTab"]': function (e) {
                var tab = parseInt($(e.currentTarget).data('tab'));
                this.selectTab(tab);
            },
            'click [data-action="removeDashlet"]': function (e) {
                var id = $(e.currentTarget).data('id');
                this.removeDashlet(id);
            },
            'click [data-action="editDashlet"]': function (e) {
                var id = $(e.currentTarget).data('id');
                var name = $(e.currentTarget).data('name');

                this.editDashlet(id, name);
            },
            'click button[data-action="editTabs"]': function () {
                this.editTabs();
            },
            'click button[data-action="addDashlet"]': function () {
                this.createView('addDashlet', 'views/modals/add-dashlet', {
                    parentType: this.model.entityType,
                }, view => {
                    view.render();

                    this.listenToOnce(view, 'add', (name) => {
                        this.addDashlet(name);
                    });
                });
            },
        },

        data: function () {
            return {
                dashboardLayout: this.dashboardLayout,
                currentTab: this.currentTab,
                isEmpty: this.isEmpty(),
            };
        },

        hasLocked: function () {
            return this.model.entityType === 'Preferences';
        },

        setup: function () {
            this.dashboardLayout = Espo.Utils.cloneDeep(this.model.get(this.name) || []);
            this.dashletsOptions = Espo.Utils.cloneDeep(this.model.get('dashletsOptions') || {});

            if (this.hasLocked()) {
                this.dashboardLocked = this.model.get('dashboardLocked') || false;
            }

            this.listenTo(this.model, 'change', () => {
                if (this.model.hasChanged(this.name)) {
                    this.dashboardLayout = Espo.Utils.cloneDeep(this.model.get(this.name) || []);
                }

                if (this.model.hasChanged('dashletsOptions')) {
                    this.dashletsOptions = Espo.Utils.cloneDeep(this.model.get('dashletsOptions') || {});
                }

                if (this.model.hasChanged(this.name)) {
                    if (this.dashboardLayout.length) {
                        if (this.isDetailMode()) {
                            this.selectTab(0);
                        }
                    }
                }

                if (this.hasLocked()) {
                    this.dashboardLocked = this.model.get('dashboardLocked') || false;
                }
            });

            this.currentTab = -1;
            this.currentTabLayout = null;

            if (this.dashboardLayout.length) {
                this.selectTab(0);
            }
        },

        selectTab: function (tab) {
            this.currentTab = tab;
            this.setupCurrentTabLayout();

            if (this.isRendered()) {
                this.reRender()
                    .then(() => {
                        this.$el
                            .find(`[data-action="selectTab"][data-tab="${tab}"]`)
                            .focus();
                    })
            }
        },

        setupCurrentTabLayout: function () {
            if (!~this.currentTab) {
                this.currentTabLayout = null;
            }

            var tabLayout = this.dashboardLayout[this.currentTab].layout || [];

            tabLayout = GridStack.Utils.sort(tabLayout);

            this.currentTabLayout = tabLayout;
        },

        addDashletHtml: function (id, name) {
            var $item = this.prepareGridstackItem(id, name);

            this.grid.addWidget(
                $item.get(0),
                {
                    x: 0,
                    y: 0,
                    w: 2 * this.WIDTH_MULTIPLIER,
                    h: 2,
                }
            );
        },

        generateId: function () {
            return (Math.floor(Math.random() * 10000001)).toString();
        },

        addDashlet: function (name) {
            var id = 'd' + (Math.floor(Math.random() * 1000001)).toString();

            if (!~this.currentTab) {
                this.dashboardLayout.push({
                    name: 'My Espo',
                    layout: [],
                    id: this.generateId(),
                });

                this.currentTab = 0;
                this.setupCurrentTabLayout();

                this.once('after:render', () => {
                    setTimeout(() => {
                        this.addDashletHtml(id, name);
                        this.fetchLayout();
                    }, 50);
                });

                this.reRender();
            }
            else {
                this.addDashletHtml(id, name);
                this.fetchLayout();
            }
        },

        removeDashlet: function (id) {
            let $item = this.$gridstack.find('.grid-stack-item[data-id="'+id+'"]');

            this.grid.removeWidget($item.get(0), true);

            var layout = this.dashboardLayout[this.currentTab].layout;

            layout.forEach((o, i) => {
                if (o.id === id) {
                    layout.splice(i, 1);
                }
            });

            delete this.dashletsOptions[id];

            this.setupCurrentTabLayout();
        },

        editTabs: function () {
            let options = {
                dashboardLayout: this.dashboardLayout,
                tabListIsNotRequired: true,
            };

            if (this.hasLocked()) {
                options.dashboardLocked = this.dashboardLocked;
            }

            this.createView('editTabs', 'views/modals/edit-dashboard', options, view => {
                view.render();

                this.listenToOnce(view, 'after:save', data => {
                    view.close();

                    let dashboardLayout = [];

                    data.dashboardTabList.forEach(name => {
                        var layout = [];
                        var id = this.generateId();

                        this.dashboardLayout.forEach(d => {
                            if (d.name === name) {
                                layout = d.layout;
                                id = d.id;
                            }
                        });

                        if (name in data.renameMap) {
                            name = data.renameMap[name];
                        }

                        dashboardLayout.push({
                            name: name,
                            layout: layout,
                            id: id,
                        });
                    });

                    this.dashboardLayout = dashboardLayout;

                    if (this.hasLocked()) {
                        this.dashboardLocked = data.dashboardLocked;
                    }

                    this.selectTab(0);

                    this.deleteNotExistingDashletsOptions();
                });
            });
        },

        deleteNotExistingDashletsOptions: function () {
            var idListMet = [];

            (this.dashboardLayout || []).forEach((itemTab) => {
                (itemTab.layout || []).forEach((item) => {
                    idListMet.push(item.id);
                });
            });

            Object.keys(this.dashletsOptions).forEach((id) => {
                if (!~idListMet.indexOf(id)) {
                    delete this.dashletsOptions[id];
                }
            });
        },

        editDashlet: function (id, name) {
            var options = this.dashletsOptions[id] || {};
            options = Espo.Utils.cloneDeep(options);

            var defaultOptions = this.getMetadata().get(['dashlets', name , 'options', 'defaults']) || {};

            Object.keys(defaultOptions).forEach((item) => {
                if (item in options) {
                    return;
                }

                options[item] = Espo.Utils.cloneDeep(defaultOptions[item]);
            });

            if (!('title' in options)) {
                options.title = this.translate(name, 'dashlets');
            }

            var optionsView = this.getMetadata().get(['dashlets', name, 'options', 'view']) ||
                'views/dashlets/options/base';

            this.createView('options', optionsView, {
                name: name,
                optionsData: options,
                fields: this.getMetadata().get(['dashlets', name, 'options', 'fields']) || {},
                userId: this.model.entityType === 'Preferences' ? this.model.id : null,
            }, view => {
                view.render();

                this.listenToOnce(view, 'save', (attributes) => {
                    this.dashletsOptions[id] = attributes;

                    view.close();

                    if ('title' in attributes) {
                        var title = attributes.title;

                        if (!title) {
                            title = this.translate(name, 'dashlets');
                        }

                        this.$el.find('[data-id="'+id+'"] .panel-title').text(title);
                    }
                });
            });
        },

        fetchLayout: function () {
            if (!~this.currentTab) {
                return;
            }

            this.dashboardLayout[this.currentTab].layout = _.map(this.$gridstack.find('.grid-stack-item'), el => {
                var $el = $(el);

                let x = $el.attr('gs-x');
                let y = $el.attr('gs-y');
                let h = $el.attr('gs-h');
                let w = $el.attr('gs-w');

                return {
                    id: $el.data('id'),
                    name: $el.data('name'),
                    x: x / this.WIDTH_MULTIPLIER,
                    y: y,
                    width: w / this.WIDTH_MULTIPLIER,
                    height: h,
                };
            });

            this.setupCurrentTabLayout();
        },

        afterRender: function () {
            if (this.currentTabLayout) {
                var $gridstack = this.$gridstack = this.$el.find('> .grid-stack');

                var grid = this.grid = GridStack.init({
                    minWidth: 4,
                    cellHeight: 60,
                    margin: 10,
                    column: 12,
                    resizable: {
                        handles: 'se',
                        helper: false
                    },
                    disableOneColumnMode: true,
                    animate: false,
                    staticGrid: this.mode !== 'edit',
                    disableResize: this.mode !== 'edit',
                    disableDrag: this.mode !== 'edit',
                });

                grid.removeAll();

                this.currentTabLayout.forEach((o) => {
                    var $item = this.prepareGridstackItem(o.id, o.name);

                    this.grid.addWidget(
                        $item.get(0),
                        {
                            x: o.x * this.WIDTH_MULTIPLIER,
                            y: o.y,
                            w: o.width * this.WIDTH_MULTIPLIER,
                            h: o.height,
                        }
                    );
                });

                $gridstack.find(' .grid-stack-item').css('position', 'absolute');

                $gridstack.on('change', (e, itemList) => {
                    this.fetchLayout();
                    this.trigger('change');
                });
            }
        },

        prepareGridstackItem: function (id, name) {
            let $item = $('<div>').addClass('grid-stack-item');
            let actionsHtml = '';

            if (this.isEditMode()) {
                actionsHtml +=
                    $('<div>')
                        .addClass('btn-group pull-right')
                        .append(
                            $('<button>')
                                .addClass('btn btn-default')
                                .attr('data-action', 'removeDashlet')
                                .attr('data-id', id)
                                .attr('title', this.translate('Remove'))
                                .append(
                                    $('<span>').addClass('fas fa-times')
                                )
                        )
                        .get(0)
                        .outerHTML;

                actionsHtml += $('<div>')
                    .addClass('btn-group pull-right')
                    .append(
                        $('<button>')
                            .addClass('btn btn-default')
                            .attr('data-action', 'editDashlet')
                            .attr('data-id', id)
                            .attr('data-name', name)
                            .attr('title', this.translate('Edit'))
                            .append(
                                $('<span>')
                                    .addClass('fas fa-pencil-alt fa-sm')
                                    .css({
                                        position: 'relative',
                                        top: '-1px',
                                    })
                            )
                    )
                    .get(0)
                    .outerHTML;
            }

            let title = this.getOption(id, 'title');

            if (!title) {
                title = this.translate(name, 'dashlets');
            }

            let headerHtml = $('<div>')
                .addClass('panel-heading')
                .append(actionsHtml)
                .append(
                    $('<h4>').addClass('panel-title').text(title)
                )
                .get(0).outerHTML;

            let $container =
                $('<div>')
                    .addClass('grid-stack-item-content panel panel-default')
                    .append(headerHtml);

            $container.attr('data-id', id);
            $container.attr('data-name', name);
            $item.attr('data-id', id);
            $item.attr('data-name', name);
            $item.append($container);

            return $item;
        },

        getOption: function (id, optionName) {
            var options = (this.model.get('dashletsOptions') || {})[id] || {};

            return options[optionName];
        },

        isEmpty: function () {
            var isEmpty = true;

            if (this.dashboardLayout && this.dashboardLayout.length) {
                this.dashboardLayout.forEach((item) => {
                    if (item.layout && item.layout.length) {
                        isEmpty = false;
                    }
                });
            }

            return isEmpty;
        },

        validateRequired: function () {
            if (!this.isRequired()) {
                return;
            }

            if (this.isEmpty()) {
                var msg = this.translate('fieldIsRequired', 'messages').replace('{field}', this.getLabelText());
                this.showValidationMessage(msg);

                return true;
            }
        },

        fetch: function () {
            let data = {};

            if (!this.dashboardLayout || !this.dashboardLayout.length) {
                data[this.name] = null;
                data['dashletsOptions'] = {};

                return data;
            }

            data[this.name] = Espo.Utils.cloneDeep(this.dashboardLayout);

            data.dashletsOptions = Espo.Utils.cloneDeep(this.dashletsOptions);

            if (this.hasLocked()) {
                data.dashboardLocked = this.dashboardLocked;
            }

            return data;
        },
    });
});
PK]�9ig!g!!views/settings/fields/tab-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ArrayFieldView from 'views/fields/array';

class TabListFieldView extends ArrayFieldView {

    addItemModalView = 'views/settings/modals/tab-list-field-add'

    noGroups = false
    noDelimiters = false

    setup() {
        super.setup();

        this.selected.forEach(item => {
            if (item && typeof item === 'object') {
                if (!item.id) {
                    item.id = this.generateItemId();
                }
            }
        });

        this.events['click [data-action="editGroup"]'] = e => {
            let id = $(e.currentTarget).parent().data('value').toString();

            this.editGroup(id);
        };
    }

    generateItemId() {
        return Math.floor(Math.random() * 1000000 + 1).toString();
    }

    setupOptions() {
        this.params.options = Object.keys(this.getMetadata().get('scopes'))
            .filter(scope => {
                if (this.getMetadata().get('scopes.' + scope + '.disabled')) {
                    return false;
                }

                if (!this.getAcl().checkScope(scope)) {
                    return false;
                }

                return this.getMetadata().get('scopes.' + scope + '.tab');
            })
            .sort((v1, v2) => {
                return this.translate(v1, 'scopeNamesPlural')
                    .localeCompare(this.translate(v2, 'scopeNamesPlural'));
            });

        if (!this.noDelimiters) {
            this.params.options.push('_delimiter_');
            this.params.options.push('_delimiter-ext_');
        }

        this.translatedOptions = {};

        this.params.options.forEach(item => {
            this.translatedOptions[item] = this.translate(item, 'scopeNamesPlural');
        });

        this.translatedOptions['_delimiter_'] = '. . .';
        this.translatedOptions['_delimiter-ext_'] = '. . .';
    }

    addValue(value) {
        if (value && typeof value === 'object') {
            if (!value.id) {
                value.id = this.generateItemId();
            }

            let html = this.getItemHtml(value);

            this.$list.append(html);
            this.selected.push(value);

            this.trigger('change');

            return;
        }

        super.addValue(value);
    }

    removeValue(value) {
        let index = this.getGroupIndexById(value);

        if (~index) {
            this.$list.children('[data-value="' + value + '"]').remove();

            this.selected.splice(index, 1);
            this.trigger('change');

            return;
        }

        super.removeValue(value);
    }

    getItemHtml(value) {
        if (value && typeof value === 'object') {
            return this.getGroupItemHtml(value);
        }

        return super.getItemHtml(value);
    }

    getGroupItemHtml(item) {
        let label = item.text || '';

        let $label = $('<span>').text(label);

        let $icon = null;

        if (item.type === 'group') {
            $icon = $('<span>')
                .addClass('far fa-list-alt')
                .addClass('text-muted')
        }

        if (item.type === 'divider') {
            $label.addClass('text-soft')
                .addClass('text-italic');
        }

        let $item = $('<span>').append($label);

        if ($icon) {
            $item.prepend(
                $icon,
                ' '
            )
        }

        return $('<div>')
            .addClass('list-group-item')
            .attr('data-value', item.id)
            .css('cursor', 'default')
            .append(
                $('<a>')
                    .attr('role', 'button')
                    .attr('tabindex', '0')
                    .attr('data-value', item.id)
                    .attr('data-action', 'editGroup')
                    .css('margin-right', '7px')
                    .append(
                        $('<span>').addClass('fas fa-pencil-alt fa-sm')
                    ),
                $item,
                '&nbsp;',
                $('<a>')
                    .addClass('pull-right')
                    .attr('role', 'button')
                    .attr('tabindex', '0')
                    .attr('data-value', item.id)
                    .attr('data-action', 'removeValue')
                    .append(
                        $('<span>').addClass('fas fa-times')
                    )
            )
            .get(0).outerHTML;
    }

    fetchFromDom() {
        let selected = [];

        this.$el.find('.list-group .list-group-item').each((i, el) => {
            let value = $(el).data('value').toString();
            let groupItem = this.getGroupValueById(value);

            if (groupItem) {
                selected.push(groupItem);

                return;
            }

            selected.push(value);
        });

        this.selected = selected;
    }

    getGroupIndexById(id) {
        for (let i = 0; i < this.selected.length; i++) {
            let item = this.selected[i];

            if (item && typeof item === 'object') {
                if (item.id === id) {
                    return i;
                }
            }
        }

        return -1;
    }

    getGroupValueById(id) {
        for (let item of this.selected) {
            if (item && typeof item === 'object') {
                if (item.id === id) {
                    return item;
                }
            }
        }

        return null;
    }

    editGroup(id) {
        let item = Espo.Utils.cloneDeep(this.getGroupValueById(id) || {});

        let index = this.getGroupIndexById(id);
        let tabList = Espo.Utils.cloneDeep(this.selected);

        let view = item.type === 'divider' ?
            'views/settings/modals/edit-tab-divider' :
            'views/settings/modals/edit-tab-group';

        this.createView('dialog', view, {itemData: item}, view => {
            view.render();

            this.listenToOnce(view, 'apply', itemData => {
                for (let a in itemData) {
                    tabList[index][a] = itemData[a];
                }

                this.model.set(this.name, tabList);

                view.close();
            });
        });
    }

    getAddItemModalOptions() {
        return {
            ...super.getAddItemModalOptions(),
            noGroups: this.noGroups,
        };
    }

    getValueForDisplay() {
        const labels = this.translatedOptions || {};

        /** @var {string[]} */
        const list = this.selected.map(item => {
            if (typeof item !== 'string') {
                return ' - ' + (item.text || '?');
            }

            return labels[item] || item;
        });

        return list.map(text => {
            return $('<div>')
                .addClass('multi-enum-item-container')
                .text(text)
                .get(0)
                .outerHTML
        }).join('');
    }
}

export default TabListFieldView;
PK]��P�__0views/settings/fields/busy-ranges-entity-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/settings/fields/busy-ranges-entity-list', ['views/fields/entity-type-list'], function (Dep) {

    return Dep.extend({

        setupOptions: function () {
            Dep.prototype.setupOptions.call(this);

            this.params.options = this.params.options.filter(scope => {
                if (this.getMetadata().get(['scopes', scope, 'disabled'])) {
                    return;
                }

                if (!this.getMetadata().get(['scopes', scope, 'object'])) {
                    return;
                }

                if (!this.getMetadata().get(['scopes', scope, 'calendar'])) {
                    return;
                }

                return true;
            })
        },
    });
});
PK]Ȏ
j��-views/settings/fields/calendar-entity-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/settings/fields/calendar-entity-list', ['views/fields/entity-type-list'], function (Dep) {

    return Dep.extend({

        setupOptions: function () {

            Dep.prototype.setupOptions.call(this);

            this.params.options = this.params.options.filter(scope => {
                if (this.getMetadata().get('scopes.' + scope + '.disabled')) return;
                if (!this.getMetadata().get('scopes.' + scope + '.object')) return;
                if (!this.getMetadata().get('scopes.' + scope + '.calendar')) return;

                return true;
            })
        },
    });
});
PK]Ġ퇂�$views/settings/fields/date-format.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/settings/fields/date-format', ['views/fields/enum'], function (Dep) {

    return Dep.extend({

        setupOptions: function () {
            this.params.options = this.getMetadata().get(['app', 'dateTime', 'dateFormatList']) || [];
        },
    });
});
PK]���		)views/settings/fields/default-currency.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/settings/fields/default-currency', ['views/fields/enum'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            this.validations.push('existing');
        },

        setupOptions: function () {
            this.params.options = Espo.Utils.clone(this.getConfig().get('currencyList') || []);
        },

        validateExisting: function () {
            var currencyList = this.model.get('currencyList');

            if (!currencyList) {
                return;
            }

            var value = this.model.get(this.name);

            if (~currencyList.indexOf(value)) {
                return;
            }

            var msg = this.translate('fieldInvalid', 'messages').replace('{field}', this.getLabelText());

            this.showValidationMessage(msg);

            return true;
        },
    });
});
PK]ܒ��++#views/settings/fields/oidc-teams.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/settings/fields/oidc-teams', ['views/fields/link-multiple-with-role'], function (Dep) {

    return Dep.extend({

        forceRoles: true,

        roleType: 'varchar',

        columnName: 'group',

        roleMaxLength: 255,

        setup: function () {
            Dep.prototype.setup.call(this);

            this.rolePlaceholderText = this.translate('IdP Group', 'labels', 'Settings');
        },
    });
});
PK]l�Ɲ/views/settings/fields/activities-entity-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/settings/fields/activities-entity-list', ['views/fields/entity-type-list'], function (Dep) {

    return Dep.extend({

        setupOptions: function () {

            Dep.prototype.setupOptions.call(this);

            this.params.options = this.params.options.filter(scope => {
                if (scope === 'Email') return;
                if (this.getMetadata().get('scopes.' + scope + '.disabled')) return;
                if (!this.getMetadata().get('scopes.' + scope + '.object')) return;
                if (!this.getMetadata().get('scopes.' + scope + '.activity')) return;

                return true;
            });
        },
    });
});
PK]6D_bbviews/settings/fields/theme.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/settings/fields/theme', ['views/fields/enum', 'theme-manager', 'ui/select'],
function (Dep, ThemeManager, /** module:ui/select*/Select) {

    return Dep.extend({

        editTemplateContent: `
            <div class="grid-auto-fit-xxs">
                <div>
                    <select data-name="{{name}}" class="form-control main-element">
                        {{options
                            params.options value
                            scope=scope
                            field=name
                            translatedOptions=translatedOptions
                            includeMissingOption=true
                            styleMap=params.style
                        }}
                    </select>
                </div>
                {{#if navbarOptionList.length}}
                <div>
                    <select data-name="themeNavbar" class="form-control">
                        {{options navbarOptionList navbar translatedOptions=navbarTranslatedOptions}}
                    </select>
                </div>
                {{/if}}
            </div>
        `,

        data: function () {
            let data = Dep.prototype.data.call(this);

            data.navbarOptionList = this.getNavbarOptionList();
            data.navbar = this.getNavbarValue() || this.getDefaultNavbar();

            data.navbarTranslatedOptions = {};
            data.navbarOptionList.forEach(item => {
                data.navbarTranslatedOptions[item] = this.translate(item, 'themeNavbars');
            });

            return data;
        },

        setup: function () {
            Dep.prototype.setup.call(this);

            this.initThemeManager();

            this.model.on('change:theme', (m, v, o) => {
                this.initThemeManager()

                if (o.ui) {
                    this.reRender()
                        .then(() => Select.focus(this.$element, {noTrigger: true}));
                }
            })
        },

        afterRenderEdit: function () {
            this.$navbar = this.$el.find('[data-name="themeNavbar"]');

            this.$navbar.on('change', () => this.trigger('change'));

            Select.init(this.$navbar);
        },

        getNavbarValue: function () {
            let params = this.model.get('themeParams') || {};

            return params.navbar;
        },

        getNavbarDefs: function () {
            if (!this.themeManager) {
                return null;
            }

            let params = this.themeManager.getParam('params');

            if (!params || !params.navbar) {
                return null;
            }

            return Espo.Utils.cloneDeep(params.navbar);
        },

        getNavbarOptionList: function () {
            let defs = this.getNavbarDefs();

            if (!defs) {
                return [];
            }

            let optionList = defs.options || [];

            if (!optionList.length || optionList.length === 1) {
                return [];
            }

            return optionList;
        },

        getDefaultNavbar: function () {
            let defs = this.getNavbarDefs() || {};

            return defs.default || null;
        },

        initThemeManager: function () {
            let theme = this.model.get('theme');

            if (!theme) {
                this.themeManager = null;

                return;
            }

            this.themeManager = new ThemeManager(
                this.getConfig(),
                this.getPreferences(),
                this.getMetadata(),
                theme
            );
        },

        getAttributeList: function () {
            return [this.name, 'themeParams'];
        },

        setupOptions: function () {
            this.params.options = Object.keys(this.getMetadata().get('themes') || {})
                .sort((v1, v2) => {
                    if (v2 === 'EspoRtl') {
                        return -1;
                    }

                    return this.translate(v1, 'theme')
                        .localeCompare(this.translate(v2, 'theme'));
                });
        },

        fetch: function () {
            let data = Dep.prototype.fetch.call(this);

            let params = {};

            if (this.$navbar.length) {
                params.navbar = this.$navbar.val();
            }

            data.themeParams = params;

            return data;
        },
    });
});
PK]��Kd	d	Cviews/settings/fields/assignment-email-notifications-entity-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/settings/fields/assignment-email-notifications-entity-list', ['views/fields/multi-enum'], function (Dep) {

    return Dep.extend({

        setup: function () {
            this.params.options = Object.keys(this.getMetadata().get('scopes'))
                .filter(scope => {
                    if (scope === 'Email') {
                        return;
                    }

                    if (this.getMetadata().get('scopes.' + scope + '.disabled')) {
                        return;
                    }

                    return this.getMetadata()
                            .get('scopes.' + scope + '.notifications') &&
                        this.getMetadata().get('scopes.' + scope + '.entity');
                })
                .sort((v1, v2) => {
                    return this.translate(v1, 'scopeNamesPlural').localeCompare(this.translate(v2, 'scopeNamesPlural'));
                });

            Dep.prototype.setup.call(this);
        },
    });
});
PK]_B���2views/settings/fields/global-search-entity-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/settings/fields/global-search-entity-list', ['views/fields/multi-enum'], function (Dep) {

    return Dep.extend({

        setup: function () {

            this.params.options = Object.keys(this.getMetadata().get('scopes'))
                .filter(scope => {
                    let defs = this.getMetadata().get(['scopes', scope]) || {};

                    if (defs.disabled || scope === 'Note') {
                        return;
                    }

                    return defs.customizable && defs.entity;
                })
                .sort((v1, v2) => {
                    return this.translate(v1, 'scopeNamesPlural')
                        .localeCompare(this.translate(v2, 'scopeNamesPlural'));
                });

            Dep.prototype.setup.call(this);
        },
    });
});
PK]�1�d}}*views/settings/fields/fiscal-year-shift.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/settings/fields/fiscal-year-shift', ['views/fields/enum-int'], function (Dep) {

    return Dep.extend({

        setupOptions: function () {
            this.params.options = [];
            this.translatedOptions = {};

            var monthNameList = this.getLanguage().get('Global', 'lists', 'monthNames') || [];

            monthNameList.forEach((name, i) => {
                this.params.options.push(i);
                this.translatedOptions[i] = name;
            });
        },
    });
});
PK]���Rs
s
'views/settings/fields/currency-rates.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/settings/fields/currency-rates', ['views/fields/base'], function (Dep) {

    return Dep.extend({

        editTemplate: 'settings/fields/currency-rates/edit',

        data: function () {
            var baseCurrency = this.model.get('baseCurrency');
            var currencyRates = this.model.get('currencyRates') || {};

            var rateValues = {};

            (this.model.get('currencyList') || []).forEach(currency => {
                if (currency !== baseCurrency) {
                    rateValues[currency] = currencyRates[currency];

                    if (!rateValues[currency]) {
                        if (currencyRates[baseCurrency]) {
                            rateValues[currency] = Math.round(1 / currencyRates[baseCurrency] * 1000) / 1000;
                        }

                        if (!rateValues[currency]) {
                            rateValues[currency] = 1.00
                        }
                    }
                }
            });

            return {
                rateValues: rateValues,
                baseCurrency: baseCurrency,
            };
        },

        setup: function () {
        },

        fetch: function () {
            var data = {};
            var currencyRates = {};

            var baseCurrency = this.model.get('baseCurrency');

            var currencyList = this.model.get('currencyList') || [];

            currencyList.forEach(currency => {
                if (currency !== baseCurrency) {
                    currencyRates[currency] = parseFloat(
                        this.$el.find('input[data-currency="'+currency+'"]').val() || 1);
                }
            });

            delete currencyRates[baseCurrency];

            for (var c in currencyRates) {
                if (!~currencyList.indexOf(c)) {
                    delete currencyRates[c];
                }
            }

            data[this.name] = currencyRates;

            return data;
        },
    });
});
PK]�z���,views/settings/fields/history-entity-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/settings/fields/history-entity-list', ['views/fields/entity-type-list'], function (Dep) {

    return Dep.extend({

        setupOptions: function () {

            Dep.prototype.setupOptions.call(this);

            this.params.options = this.params.options.filter(scope => {
                if (this.getMetadata().get('scopes.' + scope + '.disabled')) return;
                if (!this.getMetadata().get('scopes.' + scope + '.object')) return;
                if (!this.getMetadata().get('scopes.' + scope + '.activity')) return;

                return true;
            })
        },
    });
});
PK]��views/settings/edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import EditView from 'views/edit';

class SettingsEditView extends EditView {

    scope = 'Settings'

    setupHeader() {
        this.createView('header', this.headerView, {
            model: this.model,
            fullSelector: '#main > .header',
            template: this.options.headerTemplate,
            label: this.options.label,
        });
    }
}

export default SettingsEditView;
PK]%@�lTTviews/settings/record/edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/settings/record/edit', ['views/record/edit'], function (Dep) {

    return Dep.extend({

        saveAndContinueEditingAction: false,

        sideView: null,

        layoutName: 'settings',

        setup: function () {
            Dep.prototype.setup.call(this);

            this.listenTo(this.model, 'after:save', () => {
                this.getConfig().set(this.model.getClonedAttributes());
            });
        },

        afterRender: function () {
            Dep.prototype.afterRender.call(this);
        },

        exit: function (after) {
            if (after === 'cancel') {
                this.getRouter().navigate('#Admin', {trigger: true});
            }
        },
    });
});

PK]��9##'views/settings/modals/edit-tab-group.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/settings/modals/edit-tab-group', ['views/modal', 'model'], function (Dep, Model) {

    return Dep.extend({

        className: 'dialog dialog-record',

        templateContent: '<div class="record no-side-margin">{{{record}}}</div>',

        setup: function () {
            Dep.prototype.setup.call(this);

            this.headerText = this.translate('Group Tab', 'labels', 'Settings');

            this.buttonList.push({
                name: 'apply',
                label: 'Apply',
                style: 'danger',
            });

            this.buttonList.push({
                name: 'cancel',
                label: 'Cancel',
            });

            this.shortcutKeys = {
                'Control+Enter': () => this.actionApply(),
            };

            var detailLayout = [
                {
                    rows: [
                        [
                            {
                                name: 'text',
                                labelText: this.translate('label', 'fields', 'Admin'),
                            },
                            {
                                name: 'iconClass',
                                labelText: this.translate('iconClass', 'fields', 'EntityManager'),
                            },
                            {
                                name: 'color',
                                labelText: this.translate('color', 'fields', 'EntityManager'),
                            },
                        ],
                        [
                            {
                                name: 'itemList',
                                labelText: this.translate('tabList', 'fields', 'Settings'),
                            },
                            false
                        ]
                    ]
                }
            ];

            var model = this.model = new Model();

            model.name = 'GroupTab';

            model.set(this.options.itemData);

            model.setDefs({
                fields: {
                    text: {
                        type: 'varchar',
                    },
                    iconClass: {
                        type: 'base',
                        view: 'views/admin/entity-manager/fields/icon-class',
                    },
                    color: {
                        type: 'base',
                        view: 'views/fields/colorpicker',
                    },
                    itemList: {
                        type: 'array',
                        view: 'views/settings/fields/group-tab-list',
                    },
                },
            });

            this.createView('record', 'views/record/edit-for-modal', {
                detailLayout: detailLayout,
                model: model,
                selector: '.record',
            });
        },

        actionApply: function () {
            var recordView = this.getView('record');

            if (recordView.validate()) {
                return;
            }

            var data = recordView.fetch();

            this.trigger('apply', data);
        },

    });
});
PK]u�j�

)views/settings/modals/edit-tab-divider.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ModalView from 'views/modal';
import Model from 'model';

class EditTabDividerSettingsModalView extends ModalView {

    className = 'dialog dialog-record'

    templateContent = '<div class="record no-side-margin">{{{record}}}</div>'

    setup() {
        super.setup();

        this.headerText = this.translate('Divider', 'labels', 'Settings');

        this.buttonList.push({
            name: 'apply',
            label: 'Apply',
            style: 'danger',
        });

        this.buttonList.push({
            name: 'cancel',
            label: 'Cancel',
        });

        this.shortcutKeys = {
            'Control+Enter': () => this.actionApply(),
        };

        let detailLayout = [
            {
                rows: [
                    [
                        {
                            name: 'text',
                            labelText: this.translate('label', 'fields', 'Admin'),
                        },
                        false,
                    ],
                ]
            }
        ];

        let model = this.model = new Model({}, {entityType: 'Dummy'});

        model.set(this.options.itemData);
        model.setDefs({
            fields: {
                text: {
                    type: 'varchar',
                },
            },
        });

        this.createView('record', 'views/record/edit-for-modal', {
            detailLayout: detailLayout,
            model: model,
            selector: '.record',
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionApply() {
        let recordView = /** @type {module:views/record/edit}*/ this.getView('record');

        if (recordView.validate()) {
            return;
        }

        let data = recordView.fetch();

        this.trigger('apply', data);
    }
}

// noinspection JSUnusedGlobalSymbols
export default EditTabDividerSettingsModalView;
PK]�EՁ]	]	+views/settings/modals/tab-list-field-add.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ArrayFieldAddModalView from 'views/modals/array-field-add';

class TabListFieldAddSettingsModalView extends ArrayFieldAddModalView {

    setup() {
        super.setup();

        if (!this.options.noGroups) {
            this.buttonList.push({
                name: 'addGroup',
                text: this.translate('Group Tab', 'labels', 'Settings'),
            });
        }

        this.buttonList.push({
            name: 'addDivider',
            text: this.translate('Divider', 'labels', 'Settings'),
        });
    }

    actionAddGroup() {
        this.trigger('add', {
            type: 'group',
            text: this.translate('Group Tab', 'labels', 'Settings'),
            iconClass: null,
            color: null,
        });
    }

    actionAddDivider() {
        this.trigger('add', {
            type: 'divider',
            text: null,
        });
    }
}

// noinspection JSUnusedGlobalSymbols
export default TabListFieldAddSettingsModalView;
PK]]uT���)views/email-filter/fields/email-folder.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email-filter/fields/email-folder', ['views/fields/link'], function (Dep) {

    return Dep.extend({

        createDisabled: true,

        autocompleteDisabled: true,

        getSelectFilters: function () {
            if (this.getUser().isAdmin()) {
                if (this.model.get('parentType') === 'User' && this.model.get('parentId')) {
                    return {
                        assignedUser: {
                            type: 'equals',
                            attribute: 'assignedUserId',
                            value: this.model.get('parentId'),
                            data: {
                                nameValue: this.model.get('parentName'),
                            },
                        }
                    };
                }
            }
        },
    });
});
PK]R��v��#views/email-filter/fields/parent.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email-filter/fields/parent', ['views/fields/link-parent'], function (Dep) {

    return Dep.extend({

        getSelectPrimaryFilterName: function () {
            var map = {
                'User': 'active',
            };

            if (!this.foreignScope) {
                return;
            }

            return map[this.foreignScope];
        },
    });
});
PK]������#views/email-filter/fields/action.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email-filter/fields/action', ['views/fields/enum'], function (Dep) {

    return Dep.extend({});
});
PK]~�r!views/email-filter/record/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email-filter/record/list', ['views/record/list'], function (Dep) {

    return Dep.extend({

        massActionList: ['remove', 'export'],
    });
});

PK]$e�!views/email-filter/modals/edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email-filter/modals/edit', ['views/modals/edit'], function (Dep) {

    return Dep.extend({

        fullFormDisabled: true,
    });
});
PK]B����&views/event/fields/name-for-history.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/event/fields/name-for-history', ['views/fields/varchar'], function (Dep) {

    return Dep.extend({

        listLinkTemplate: 'event/fields/name-for-history/list-link',

        data: function () {
            let data = Dep.prototype.data.call(this);

            let status = this.model.get('status');

            let canceledStatusList = this.getMetadata()
                .get(['scopes', this.model.entityType, 'canceledStatusList']) || [];

            data.strikethrough = canceledStatusList.includes(status);

            return data;
        },
    });
});
PK]�8�O--$views/personal-data/record/record.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/personal-data/record/record', ['views/record/base'], function (Dep) {

    return Dep.extend({

        template: 'personal-data/record/record',

        additionalEvents: {
            'click .checkbox': function (e) {
                var name = $(e.currentTarget).data('name');

                if (e.currentTarget.checked) {
                    if (!~this.checkedFieldList.indexOf(name)) {
                        this.checkedFieldList.push(name);
                    }

                    if (this.checkedFieldList.length === this.fieldList.length) {
                        this.$el.find('.checkbox-all').prop('checked', true);
                    } else {
                        this.$el.find('.checkbox-all').prop('checked', false);
                    }
                } else {
                    var index = this.checkedFieldList.indexOf(name);

                    if (~index) {
                        this.checkedFieldList.splice(index, 1);
                    }

                    this.$el.find('.checkbox-all').prop('checked', false);
                }

                this.trigger('check', this.checkedFieldList);
            },

            'click .checkbox-all': function (e) {
                if (e.currentTarget.checked) {
                    this.checkedFieldList = Espo.Utils.clone(this.fieldList);

                    this.$el.find('.checkbox').prop('checked', true);
                } else {
                    this.checkedFieldList = [];

                    this.$el.find('.checkbox').prop('checked', false);
                }

                this.trigger('check', this.checkedFieldList);
            },
        },

        data: function () {
            var data = {};

            data.fieldDataList = this.getFieldDataList();
            data.scope = this.scope;
            data.editAccess = this.editAccess;

            return data;
        },

        setup: function () {
            Dep.prototype.setup.call(this);

            this.events = {
                ...this.additionalEvents,
                ...this.events,
            };

            this.scope = this.model.entityType;

            this.fieldList = [];
            this.checkedFieldList = [];

            this.editAccess = this.getAcl().check(this.model, 'edit');

            var fieldDefs = this.getMetadata().get(['entityDefs', this.scope, 'fields']) || {};

            var fieldList = [];

            for (var field in fieldDefs) {
                var defs = fieldDefs[field];

                if (defs.isPersonalData) {
                    fieldList.push(field);
                }
            }

            fieldList.forEach(field => {
                var type = fieldDefs[field].type;
                var attributeList = this.getFieldManager().getActualAttributeList(type, field);

                var isNotEmpty = false;

                attributeList.forEach(attribute => {
                    var value = this.model.get(attribute);

                    if (value) {
                        if (Object.prototype.toString.call(value) === '[object Array]') {
                            if (value.length) {
                                return;
                            }
                        }

                        isNotEmpty = true;
                    }
                });

                var hasAccess = !~this.getAcl().getScopeForbiddenFieldList(this.scope, 'view').indexOf(field);

                if (isNotEmpty && hasAccess) {
                    this.fieldList.push(field);
                }
            });

            this.fieldList = this.fieldList.sort((v1, v2) => {
                return this.translate(v1, 'fields', this.scope)
                    .localeCompare(this.translate(v2, 'fields', this.scope));
            });

            this.fieldList.forEach(field => {
                this.createField(field, null, null, 'detail', true);
            });
        },

        getFieldDataList: function () {
            var forbiddenList = this.getAcl().getScopeForbiddenFieldList(this.scope, 'edit');

            var list = [];

            this.fieldList.forEach(field => {
                list.push({
                    name: field,
                    key: field + 'Field',
                    editAccess: this.editAccess && !~forbiddenList.indexOf(field),
                });
            });

            return list;
        },
    });
});
PK]r���+views/personal-data/modals/personal-data.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/personal-data/modals/personal-data', ['views/modal'], function (Dep) {

    return Dep.extend({

        className: 'dialog dialog-record',

        template: 'personal-data/modals/personal-data',

        backdrop: true,

        setup: function () {
            Dep.prototype.setup.call(this);

            this.buttonList = [
                {
                    name: 'cancel',
                    label: 'Close'
                },
            ];

            this.headerText = this.getLanguage().translate('Personal Data');
            this.headerText += ': ' + this.model.get('name');

            if (this.getAcl().check(this.model, 'edit')) {
                this.buttonList.unshift({
                    name: 'erase',
                    label: 'Erase',
                    style: 'danger',
                    disabled: true,
                });
            }

            this.fieldList = [];

            this.scope = this.model.entityType;

            this.createView('record', 'views/personal-data/record/record', {
                selector: '.record',
                model: this.model
            }, (view) => {
                this.listenTo(view, 'check', (fieldList) => {
                    this.fieldList = fieldList;

                    if (fieldList.length) {
                        this.enableButton('erase');
                    } else {
                        this.disableButton('erase');
                    }
                });

                if (!view.fieldList.length) {
                    this.disableButton('export');
                }
            });
        },

        actionErase: function () {
            this.confirm({
                message: this.translate('erasePersonalDataConfirmation', 'messages'),
                confirmText: this.translate('Erase')
            }, () => {
                this.disableButton('erase');

                Espo.Ajax.postRequest('DataPrivacy/action/erase', {
                    fieldList: this.fieldList,
                    entityType: this.scope,
                    id: this.model.id,
                }).then(() => {
                    Espo.Ui.success(this.translate('Done'));

                    this.trigger('erase');
                })
                .catch(() => {
                    this.enableButton('erase');
                });
            });
        },
    });
});
PK]���ߵt�tviews/modal.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/modal */

import View from 'view';

/**
 * A base modal view. Can be extended or used directly.
 *
 * @see https://docs.espocrm.com/development/modal/
 */
class ModalView extends View {

    /**
     * A button or dropdown action item.
     *
     * @typedef {Object} module:views/modal~Button
     *
     * @property {string} name A name.
     * @property {string} [label] A label. To be translated
     *   (with a scope defined in the `scope` class property).
     * @property {string} [text] A text (not translated).
     * @property {string} [labelTranslation] A label translation path.
     * @property {string} [html] HTML.
     * @property {boolean} [pullLeft=false] Deprecated. Use the `position` property.
     * @property {'left'|'right'} [position='left'] A position.
     * @property {'default'|'danger'|'success'|'warning'} [style='default'] A style.
     * @property {boolean} [hidden=false] Is hidden.
     * @property {boolean} [disabled=false] Disabled.
     * @property {function(module:ui.Dialog): void} [onClick] Called on click. If not defined, then
     * the `action<Name>` class method will be called.
     * @property {string} [className] An additional class name.
     * @property {string} [title] A title text.
     * @property {'primary'|'danger'|'success'|'warning'|'text'} [style] A style.
     */

    /**
     * @typedef {Object} module:views/modal~Options
     * @property {string} [headerText] A header text.
     * @property {HTMLElement} [headerElement] A header element.
     * @property {'static'|boolean} [backdrop] A backdrop.
     * @property {module:views/modal~Button} [buttonList] Buttons.
     * @property {module:views/modal~Button} [dropdownItemList] Buttons.
     */

    /**
     * @param {module:views/modal~Options | Option.<string, *>} [options] Options.
     */
    constructor(options) {
        super(options);
    }

    /**
     * A CSS name.
     *
     * @protected
     */
    cssName = 'modal-dialog'

    /**
     * A class-name. Use `'dialog dialog-record'` for modals containing a record form.
     *
     * @protected
     */
    className = 'dialog'

    /**
     * @protected
     * @deprecated Use `headerHtml`
     */
    header

    /**
     * A header HTML. Beware of XSS.
     *
     * @protected
     * @type {string|null}
     */
    headerHtml

    /**
     * A header JQuery instance.
     *
     * @protected
     * @type {JQuery}
     */
    $header

    /**
     * A header element.
     *
     * @protected
     * @type {Element}
     */
    headerElement

    /**
     * A header text.
     *
     * @protected
     * @type {string}
     */
    headerText

    /**
     * A dialog instance.
     *
     * @protected
     * @type {Espo.Ui.Dialog}
     */
    dialog

    /**
     * A container selector.
     *
     * @protected
     * @type {string}
     */
    containerSelector = ''

    /**
     * A scope name. Used when translating button labels.
     *
     * @type {string|null}
     */
    scope = null

    /**
     * A backdrop.
     *
     * @protected
     * @type {'static'|boolean}
     */
    backdrop = 'static'

    /**
     * Buttons.
     *
     * @protected
     * @type {module:views/modal~Button[]}
     */
    buttonList = []

    /**
     * Dropdown action items.
     *
     * @protected
     * @type {Array<module:views/modal~Button|false>}
     */
    dropdownItemList = []

    /**
     * @deprecated Use `buttonList`.
     * @protected
     * @todo Remove.
     */
    buttons = []

    /**
     * A width.
     *
     * @protected
     * @type {number|null}
     */
    width = null

    /**
     * Not used.
     *
     * @deprecated
     */
    fitHeight = false

    /**
     * To disable fitting to a window height.
     *
     * @protected
     * @type {boolean}
     */
    noFullHeight = false

    /**
     * Disable the ability to close by pressing the `Esc` key.
     *
     * @protected
     * @type {boolean}
     */
    escapeDisabled = false

    /**
     * Is draggable.
     *
     * @protected
     * @type {boolean}
     */
    isDraggable = false

    /**
     * Is collapsable.
     *
     * @protected
     * @type {boolean}
     */
    isCollapsable = false

    /**
     * Is collapsed. Do not change value. Only for reading.
     *
     * @protected
     * @type {boolean}
     */
    isCollapsed = false

    /**
     * @inheritDoc
     */
    events = {
        /** @this module:views/modal */
        'click .action': function (e) {
            Espo.Utils.handleAction(this, e.originalEvent, e.currentTarget);
        },
        /** @this module:views/modal */
        'click [data-action="collapseModal"]': function () {
            this.collapse();
        },
    }

    /**
     * @protected
     * @type {boolean|null}
     */
    footerAtTheTop = null

    /**
     * A shortcut-key => action map.
     *
     * @protected
     * @type {?Object.<string,string|function (JQueryKeyEventObject): void>}
     */
    shortcutKeys = null

    /**
     * @inheritDoc
     */
    init() {
        const id = this.cssName + '-container-' + Math.floor((Math.random() * 10000) + 1).toString();

        this.containerSelector = '#' + id;

        this.header = this.options.header || this.header;
        this.headerHtml = this.options.headerHtml || this.headerHtml;
        this.$header = this.options.$header || this.$header;
        this.headerElement = this.options.headerElement || this.headerElement;
        this.headerText = this.options.headerText || this.headerText;

        this.backdrop = this.options.backdrop || this.backdrop;

        this.setSelector(this.containerSelector);

        this.buttonList = this.options.buttonList || this.buttonList;
        this.dropdownItemList = this.options.dropdownItemList || this.dropdownItemList;

        this.buttonList = Espo.Utils.cloneDeep(this.buttonList);
        this.dropdownItemList = Espo.Utils.cloneDeep(this.dropdownItemList);

        // @todo Remove in v9.0.
        this.buttons = Espo.Utils.cloneDeep(this.buttons);

        if (this.shortcutKeys) {
            this.shortcutKeys = Espo.Utils.cloneDeep(this.shortcutKeys);
        }

        this.on('render', () => {
            if (this.dialog) {
                this.dialog.close();
            }

            this.isCollapsed = false;

            $(this.containerSelector).remove();

            $('<div />').css('display', 'none')
                .attr('id', id)
                .addClass('modal-container')
                .appendTo('body');

            let modalBodyDiffHeight = 92;

            if (this.getThemeManager().getParam('modalBodyDiffHeight') !== null) {
                modalBodyDiffHeight = this.getThemeManager().getParam('modalBodyDiffHeight');
            }

            let headerHtml = this.headerHtml || this.header;

            if (this.$header && this.$header.length) {
                headerHtml = this.$header.get(0).outerHTML;
            }

            if (this.headerElement) {
                headerHtml = this.headerElement.outerHTML;
            }

            if (this.headerText) {
                headerHtml = Handlebars.Utils.escapeExpression(this.headerText);
            }

            let footerAtTheTop = (this.footerAtTheTop !== null) ? this.footerAtTheTop :
                this.getThemeManager().getParam('modalFooterAtTheTop');

            this.dialog = new Espo.Ui.Dialog({
                backdrop: this.backdrop,
                header: headerHtml,
                container: this.containerSelector,
                body: '',
                buttonList: this.getDialogButtonList(),
                dropdownItemList: this.getDialogDropdownItemList(),
                width: this.width,
                keyboard: !this.escapeDisabled,
                fitHeight: this.fitHeight,
                draggable: this.isDraggable,
                className: this.className,
                bodyDiffHeight: modalBodyDiffHeight,
                footerAtTheTop: footerAtTheTop,
                fullHeight: !this.noFullHeight && this.getThemeManager().getParam('modalFullHeight'),
                screenWidthXs: this.getThemeManager().getParam('screenWidthXs'),
                fixedHeaderHeight: this.fixedHeaderHeight,
                closeButton: !this.noCloseButton,
                collapseButton: this.isCollapsable,
                onRemove: () => this.onDialogClose(),
                onBackdropClick: () => this.onBackdropClick(),
            });

            this.setElement(this.containerSelector + ' .body');
        });

        this.on('after:render', () => {
            $(this.containerSelector).show();

            this.dialog.show();

            if (this.fixedHeaderHeight && this.flexibleHeaderFontSize) {
                this.adjustHeaderFontSize();
            }

            this.adjustButtons();

            if (!this.noFullHeight) {
                this.initBodyScrollListener();
            }
        });

        this.once('remove', () => {
            if (this.dialog) {
                this.dialog.close();
            }

            $(this.containerSelector).remove();
        });
    }

    setupFinal() {
        if (this.shortcutKeys) {
            this.events['keydown.modal-base'] = e => {
                let key = Espo.Utils.getKeyFromKeyEvent(e);

                if (typeof this.shortcutKeys[key] === 'function') {
                    this.shortcutKeys[key].call(this, e.originalEvent);

                    return;
                }

                let actionName = this.shortcutKeys[key];

                if (!actionName) {
                    return;
                }

                if (this.hasActionItem(actionName) && !this.hasAvailableActionItem(actionName)) {
                    return;
                }

                e.preventDefault();
                e.stopPropagation();

                let methodName = 'action' + Espo.Utils.upperCaseFirst(actionName);

                if (typeof this[methodName] === 'function') {
                    this[methodName]();

                    return;
                }

                this[actionName]();
            };
        }
    }

    /**
     * Get a button list for a dialog.
     *
     * @private
     * @return {module:ui.Dialog~Button[]}
     */
    getDialogButtonList() {
        let buttonListExt = [];

        // @todo remove it as deprecated.
        this.buttons.forEach(item => {
            let o = Espo.Utils.clone(item);

            if (!('text' in o) && ('label' in o)) {
                o.text = this.getLanguage().translate(o.label);
            }

            buttonListExt.push(o);
        });

        this.buttonList.forEach(item => {
            let o = {};

            if (typeof item === 'string') {
                o.name = /** @type string */item;
            } else if (typeof item === 'object') {
                o = item;
            } else {
                return;
            }

            if (!o.text) {
                if (o.labelTranslation) {
                    o.text = this.getLanguage().translatePath(o.labelTranslation);
                }
                else if ('label' in o) {
                    o.text = this.translate(o.label, 'labels', this.scope);
                }
                else {
                    o.text = this.translate(o.name, 'modalActions', this.scope);
                }
            }

            o.onClick = o.onClick || ((d, e) => {
                let handler = o.handler || (o.data || {}).handler;

                Espo.Utils.handleAction(this, e.originalEvent, e.currentTarget, {
                    action: o.name,
                    handler: handler,
                });
            });

            buttonListExt.push(o);
        });

        return buttonListExt;
    }

    /**
     * Get a dropdown item list for a dialog.
     *
     * @private
     * @return {module:ui.Dialog~Button[]}
     */
    getDialogDropdownItemList() {
        let dropdownItemListExt = [];

        this.dropdownItemList.forEach(item => {
            let o = {};

            if (typeof item === 'string') {
                o.name = /** @type string */item;
            } else if (typeof item === 'object') {
                o = item;
            } else {
                return;
            }

            if (!o.text) {
                if (o.labelTranslation) {
                    o.text = this.getLanguage().translatePath(o.labelTranslation);
                }
                else if ('label' in o) {
                    o.text = this.translate(o.label, 'labels', this.scope)
                }
                else {
                    o.text = this.translate(o.name, 'modalActions', this.scope);
                }
            }

            o.onClick = o.onClick || ((d, e) => {
                let handler = o.handler || (o.data || {}).handler;

                Espo.Utils.handleAction(this, e.originalEvent, e.currentTarget, {
                    action: o.name,
                    handler: handler,
                });
            });

            dropdownItemListExt.push(o);
        });

        return dropdownItemListExt;
    }

    /** @private */
    updateDialog() {
        if (!this.dialog) {
            return;
        }

        this.dialog.setActionItems(
            this.getDialogButtonList(),
            this.getDialogDropdownItemList()
        );
    }

    /** @private */
    onDialogClose() {
        if (!this.isBeingRendered() && !this.isCollapsed) {
            this.trigger('close');
            this.remove();
        }
    }

    /**
     * @protected
     */
    onBackdropClick() {}

    /**
     * A `cancel` action.
     */
    actionCancel() {
        this.trigger('cancel');
        this.dialog.close();
    }

    /**
     * A `close` action.
     */
    actionClose() {
        this.trigger('cancel');
        this.dialog.close();
    }

    /**
     * Close a dialog.
     */
    close() {
        this.dialog.close();
    }

    /**
     * Disable a button.
     *
     * @param {string} name A button name.
     */
    disableButton(name) {
        this.buttonList.forEach((d) => {
            if (d.name !== name) {
                return;
            }

            d.disabled = true;
        });

        if (!this.isRendered()) {
            return;
        }

        this.$el.find('footer button[data-name="'+name+'"]')
            .addClass('disabled')
            .attr('disabled', 'disabled');
    }

    /**
     * Enable a button.
     *
     * @param {string} name A button name.
     */
    enableButton(name) {
        this.buttonList.forEach((d) => {
            if (d.name !== name) {
                return;
            }

            d.disabled = false;
        });

        if (!this.isRendered()) {
            return;
        }

        this.$el.find('footer button[data-name="'+name+'"]')
            .removeClass('disabled')
            .removeAttr('disabled');
    }

    /**
     * Add a button.
     *
     * @param {module:views/modal~Button} o Button definitions.
     * @param {boolean|string} [position=false] True prepends, false appends. If a string
     *   then will be added after a button with a corresponding name.
     * @param {boolean} [doNotReRender=false] Do not re-render.
     */
    addButton(o, position, doNotReRender) {
        let index = -1;

        this.buttonList.forEach((item, i) => {
            if (item.name === o.name) {
                index = i;
            }
        });

        if (~index) {
            return;
        }

        if (position === true) {
            this.buttonList.unshift(o);
        }
        else if (typeof position === 'string') {
            index = -1;

            this.buttonList.forEach((item, i) => {
                if (item.name === position) {
                    index = i;
                }
            });

            if (~index) {
                this.buttonList.splice(index, 0, o);
            } else {
                this.buttonList.push(o);
            }
        }
        else {
            this.buttonList.push(o);
        }

        if (!doNotReRender && this.isRendered()) {
            this.reRenderFooter();
        }
    }

    /**
     * Add a dropdown item.
     *
     * @param {module:views/modal~Button} o Button definitions.
     * @param {boolean} [toBeginning=false] To prepend.
     * @param {boolean} [doNotReRender=false] Do not re-render.
     */
    addDropdownItem(o, toBeginning, doNotReRender) {
        if (!o) {
            toBeginning ?
                this.dropdownItemList.unshift(false) :
                this.dropdownItemList.push(false);

            return;
        }

        let name = o.name;

        if (!name) {
            return;
        }

        for (let item of this.dropdownItemList) {
            if (item.name === name) {
                return;
            }
        }

        toBeginning ?
            this.dropdownItemList.unshift(o) :
            this.dropdownItemList.push(o);

        if (!doNotReRender && this.isRendered()) {
            this.reRenderFooter();
        }
    }

    /** @private */
    reRenderFooter() {
        if (!this.dialog) {
            return;
        }

        this.updateDialog();

        let $footer = this.dialog.getFooter();

        this.$el.find('footer.modal-footer')
            .empty()
            .append($footer);

        this.dialog.initButtonEvents();
    }

    /**
     * Remove a button or a dropdown action item.
     *
     * @param {string} name A name.
     * @param {boolean} [doNotReRender=false] Do not re-render.
     */
    removeButton(name, doNotReRender) {
        let index = -1;

        for (const [i, item] of this.buttonList.entries()) {
            if (item.name === name) {
                index = i;

                break;
            }
        }

        if (~index) {
            this.buttonList.splice(index, 1);
        }

        for (const [i, item] of this.dropdownItemList.entries()) {
            if (item.name === name) {
                this.dropdownItemList.splice(i, 1);

                break;
            }
        }

        if (this.isRendered()) {
            this.$el.find('.modal-footer [data-name="'+name+'"]').remove();
        }

        if (!doNotReRender && this.isRendered()) {
            this.reRender();
        }
    }

    /**
     * @deprecated Use `showActionItem`.
     *
     * @protected
     * @param {string} name
     */
    showButton(name) {
        for (let item of this.buttonList) {
            if (item.name === name) {
                item.hidden = false;

                break;
            }
        }

        if (!this.isRendered()) {
            return;
        }

        this.$el.find('footer button[data-name="' + name + '"]').removeClass('hidden');

        this.adjustButtons();
    }

    /**
     * @deprecated Use `hideActionItem`.
     *
     * @protected
     * @param {string} name
     */
    hideButton(name) {
        for (let item of this.buttonList) {
            if (item.name === name) {
                item.hidden = true;

                break;
            }
        }

        if (!this.isRendered()) {
            return;
        }

        this.$el.find('footer button[data-name="'+name+'"]').addClass('hidden');

        this.adjustButtons();
    }

    /**
     * Show an action item (button or dropdown item).
     *
     * @param {string} name A name.
     */
    showActionItem(name) {
        for (let item of this.buttonList) {
            if (item.name === name) {
                item.hidden = false;

                break;
            }
        }

        for (let item of this.dropdownItemList) {
            if (item.name === name) {
                item.hidden = false;

                break;
            }
        }

        if (!this.isRendered()) {
            return;
        }

        this.$el.find('footer button[data-name="'+name+'"]').removeClass('hidden');
        this.$el.find('footer li > a[data-name="'+name+'"]').parent().removeClass('hidden');

        if (!this.isDropdownItemListEmpty()) {
            let $dropdownGroup = this.$el.find('footer .main-btn-group > .btn-group');

            $dropdownGroup.removeClass('hidden');
            $dropdownGroup.find('> button').removeClass('hidden');
        }

        this.adjustButtons();
    }

    /**
     * Hide an action item (button or dropdown item).
     *
     * @param {string} name A name.
     */
    hideActionItem(name) {
        for (let item of this.buttonList) {
            if (item.name === name) {
                item.hidden = true;

                break;
            }
        }

        for (let item of this.dropdownItemList) {
            if (item.name === name) {
                item.hidden = true;

                break;
            }
        }

        if (!this.isRendered()) {
            return;
        }

        this.$el.find('footer button[data-name="'+name+'"]').addClass('hidden');
        this.$el.find('footer li > a[data-name="'+name+'"]').parent().addClass('hidden');

        if (this.isDropdownItemListEmpty()) {
            let $dropdownGroup = this.$el.find('footer .main-btn-group > .btn-group');

            $dropdownGroup.addClass('hidden');
            $dropdownGroup.find('> button').addClass('hidden');
        }

        this.adjustButtons();
    }

    /**
     * Whether an action item exists (hidden, disabled or not).
     *
     * @param {string} name An action item name.
     */
    hasActionItem(name) {
        let hasButton = this.buttonList
            .findIndex(item => item.name === name) !== -1;

        if (hasButton) {
            return true;
        }

        return this.dropdownItemList
            .findIndex(item => item.name === name) !== -1;
    }

    /**
     * Whether an action item is visible and not disabled.
     *
     * @param {string} name An action item name.
     */
    hasAvailableActionItem(name) {
        let hasButton = this.buttonList
            .findIndex(item => item.name === name && !item.disabled && !item.hidden) !== -1;

        if (hasButton) {
            return true;
        }

        return this.dropdownItemList
            .findIndex(item => item.name === name && !item.disabled && !item.hidden) !== -1;
    }

    /**
     * @private
     * @return {boolean}
     */
    isDropdownItemListEmpty() {
        if (this.dropdownItemList.length === 0) {
            return true;
        }

        let isEmpty = true;

        this.dropdownItemList.forEach((item) => {
            if (!item.hidden) {
                isEmpty = false;
            }
        });

        return isEmpty;
    }

    /**
     * @private
     * @param {number} [step=0]
     */
    adjustHeaderFontSize(step) {
        step = step || 0;

        if (!step) {
            this.fontSizePercentage = 100;
        }

        let $titleText = this.$el.find('.modal-title > .modal-title-text');

        let containerWidth = $titleText.parent().width();
        let textWidth = 0;

        $titleText.children().each((i, el) => {
            textWidth += $(el).outerWidth(true);
        });

        if (containerWidth < textWidth) {
            if (step > 5) {
                let $title = this.$el.find('.modal-title');

                $title.attr('title', $titleText.text());
                $title.addClass('overlapped');

                $titleText.children().each((i, el) => {
                   $(el).removeAttr('title');
                });

                return;
            }

            this.fontSizePercentage -= 4;

            this.$el.find('.modal-title .font-size-flexible')
                .css('font-size', this.fontSizePercentage + '%');

            this.adjustHeaderFontSize(step + 1);
        }
    }

    /**
     * Collapse.
     */
    collapse() {
        this.beforeCollapse().then(data => {
            if (!this.getParentView()) {
                throw new Error("Can't collapse w/o parent view.");
            }

            this.isCollapsed = true;

            data = data || {};

            let title;

            if (data.title) {
                title = data.title;
            }
            else {
                let $title = this.$el.find('.modal-header .modal-title .modal-title-text');

                /*if ($title.children().length) {
                    $title.children()[0];
                }*/

                title = $title.text();
            }

            this.dialog.close();

            let masterView = this;

            while (masterView.getParentView()) {
                masterView = masterView.getParentView();
            }

            this.unchainFromParent();

            (new Promise(resolve => {
                if (masterView.hasView('collapsedModalBar')) {
                    resolve(masterView.getView('collapsedModalBar'));

                    return;
                }

                masterView
                    .createView('collapsedModalBar', 'views/collapsed-modal-bar', {
                        fullSelector: 'body > .collapsed-modal-bar',
                    })
                    .then(view => resolve(view));
            }))
            .then(barView => {
                barView.addModalView(this, {title: title});
            });
        });
    }

    unchainFromParent() {
        const key = this.getParentView().getViewKey(this);

        this.getParentView().unchainView(key);
    }

    /**
     * Called before collapse. Can be extended to execute some logic, e.g. save form data.
     *
     * @protected
     * @return {Promise}
     */
    beforeCollapse() {
        return new Promise(resolve => resolve());
    }

    /** @private */
    adjustButtons() {
        this.adjustLeftButtons();
        this.adjustRightButtons();
    }

    /** @private */
    adjustLeftButtons() {
        let $buttons = this.$el.find('footer.modal-footer > .main-btn-group button.btn');

        $buttons
            .removeClass('radius-left')
            .removeClass('radius-right');

        let $buttonsVisible = $buttons.filter('button:not(.hidden)');

        $buttonsVisible.first().addClass('radius-left');
        $buttonsVisible.last().addClass('radius-right');
    }

    /** @private */
    adjustRightButtons() {
        let $buttons = this.$el.find('footer.modal-footer > .additional-btn-group button.btn:not(.btn-text)');

        $buttons
            .removeClass('radius-left')
            .removeClass('radius-right')
            .removeClass('margin-right');

        let $buttonsVisible = $buttons.filter('button:not(.hidden)');

        $buttonsVisible.first().addClass('radius-left');
        $buttonsVisible.last().addClass('radius-right');

        if ($buttonsVisible.last().next().hasClass('btn-text')) {
            $buttonsVisible.last().addClass('margin-right');
        }
    }

    /**
     * @protected
     */
    initBodyScrollListener() {
        let $body = this.$el.find('> .dialog > .modal-dialog > .modal-content > .modal-body');
        let $footer = $body.parent().find('> .modal-footer');

        if (!$footer.length) {
            return;
        }

        $body.off('scroll.footer-shadow');

        $body.on('scroll.footer-shadow', () => {
            if ($body.scrollTop()) {
                $footer.addClass('shadowed');

                return;
            }

            $footer.removeClass('shadowed');
        });
    }
}

export default ModalView;
PK]�69u�!�!views/email-folder/list-side.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email-folder/list-side', ['view'], function (Dep) {

    return Dep.extend({

        template: 'email-folder/list-side',

        FOLDER_ALL: 'all',
        FOLDER_INBOX: 'inbox',
        FOLDER_DRAFTS: 'drafts',

        events: {
            'click [data-action="selectFolder"]': function (e) {
                e.preventDefault();

                let id = $(e.currentTarget).data('id');

                this.actionSelectFolder(id);
            }
        },

        data: function () {
            let data = {};

            data.selectedFolderId = this.selectedFolderId;
            data.showEditLink = this.options.showEditLink;
            data.scope = this.scope;

            return data;
        },

        actionSelectFolder: function (id) {
            this.$el.find('li.selected').removeClass('selected');

            this.selectFolder(id);

            this.$el.find('li[data-id="'+id+'"]').addClass('selected');
        },

        setup: function () {
            this.scope = 'EmailFolder';
            this.selectedFolderId = this.options.selectedFolderId || this.FOLDER_ALL;
            this.emailCollection = this.options.emailCollection;

            this.loadNotReadCounts();

            this.listenTo(this.emailCollection, 'sync', this.loadNotReadCounts);
            this.listenTo(this.emailCollection, 'folders-update', this.loadNotReadCounts);

            this.listenTo(this.emailCollection, 'all-marked-read', () => {
                this.countsData = this.countsData || {};

                for (let id in this.countsData) {
                    if (id === this.FOLDER_DRAFTS) {
                        continue;
                    }

                    this.countsData[id] = 0;
                }

                this.renderCounts();
            });

            this.listenTo(this.emailCollection, 'draft-sent', () => {
                this.decreaseNotReadCount(this.FOLDER_DRAFTS);
                this.renderCounts();
            });

            this.listenTo(this.emailCollection, 'change:isRead', model => {
                if (this.countsIsBeingLoaded) {
                    return;
                }

                this.manageCountsDataAfterModelChanged(model);
            });

            this.listenTo(this.emailCollection, 'model-removing', id => {
                let model = this.emailCollection.get(id);

                if (!model) {
                    return;
                }

                if (this.countsIsBeingLoaded) {
                    return;
                }

                this.manageModelRemoving(model);
            });

            this.listenTo(this.emailCollection, 'moving-to-trash', (id, model) => {
                model = this.emailCollection.get(id) || model;

                if (!model) {
                    return;
                }

                if (this.countsIsBeingLoaded) {
                    return;
                }

                this.manageModelRemoving(model);
            });

            this.listenTo(this.emailCollection, 'retrieving-from-trash', (id, model) => {
                model = this.emailCollection.get(id) || model;

                if (!model) {
                    return;
                }

                if (this.countsIsBeingLoaded) {
                    return;
                }

                this.manageModelRetrieving(model);
            });
        },

        manageModelRemoving: function (model) {
            if (model.get('status') === 'Draft') {
                this.decreaseNotReadCount(this.FOLDER_DRAFTS);
                this.renderCounts();

                return;
            }

            if (!model.get('isUsers')) {
                return;
            }

            if (model.get('isRead')) {
                return;
            }

            let folderId = model.get('groupFolderId') ?
                ('group:' + model.get('groupFolderId')) :
                (model.get('folderId') || this.FOLDER_INBOX);

            this.decreaseNotReadCount(folderId);
            this.renderCounts();
        },

        manageModelRetrieving: function (model) {
            if (!model.get('isUsers')) {
                return;
            }

            if (model.get('isRead')) {
                return;
            }

            let folderId = model.get('groupFolderId') ?
                ('group:' + model.get('groupFolderId')) :
                (model.get('folderId') || this.FOLDER_INBOX);

            this.increaseNotReadCount(folderId);
            this.renderCounts();
        },

        manageCountsDataAfterModelChanged: function (model) {
            if (!model.get('isUsers')) {
                return;
            }

            let folderId = model.get('groupFolderId') ?
                ('group:' + model.get('groupFolderId')) :
                (model.get('folderId') || this.FOLDER_INBOX);

            !model.get('isRead') ?
                this.increaseNotReadCount(folderId) :
                this.decreaseNotReadCount(folderId);

            this.renderCounts();
        },

        increaseNotReadCount: function (folderId) {
            this.countsData = this.countsData || {};
            this.countsData[folderId] = this.countsData[folderId] || 0;
            this.countsData[folderId]++;
        },

        decreaseNotReadCount: function (folderId) {
            this.countsData = this.countsData || {};

            this.countsData[folderId] = this.countsData[folderId] || 0;

            if (this.countsData[folderId]) {
                this.countsData[folderId]--;
            }
        },

        selectFolder: function (id) {
            this.emailCollection.reset();
            this.emailCollection.abortLastFetch();

            this.selectedFolderId = id;
            this.trigger('select', id);
        },

        afterRender: function () {
            if (this.countsData) {
                this.renderCounts();
            }
        },

        loadNotReadCounts: function () {
            if (this.countsIsBeingLoaded) {
                return;
            }

            this.countsIsBeingLoaded = true;

            Espo.Ajax.getRequest('Email/inbox/notReadCounts').then(data => {
                this.countsData = data;

                if (this.isRendered()) {
                    this.renderCounts();
                    this.countsIsBeingLoaded = false;

                    return;
                }

                this.once('after:render', () => {
                    this.renderCounts();
                    this.countsIsBeingLoaded = false;
                });
            });
        },

        renderCounts: function () {
            let data = this.countsData;

            for (let id in data) {
                let value = '';

                if (data[id]) {
                    value = data[id].toString();
                }

                this.$el.find('li a.count[data-id="'+id+'"]').text(value);
            }
        },
    });
});
PK]�\����0views/email-folder/record/row-actions/default.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email-folder/record/row-actions/default', ['views/record/row-actions/default'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);
        },

        getActionList: function () {
            var list = Dep.prototype.getActionList.call(this);

            if (this.options.acl.edit) {
                list.unshift({
                    action: 'moveDown',
                    label: 'Move Down',
                    data: {
                        id: this.model.id,
                    },
                });

                list.unshift({
                    action: 'moveUp',
                    label: 'Move Up',
                    data: {
                        id: this.model.id,
                    },
                });
            }

            return list;
        },
    });
});
PK],��
�
!views/email-folder/record/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email-folder/record/list', ['views/record/list'], function (Dep) {

    return Dep.extend({

        massUpdateDisabled: true,

        massRemoveDisabled: true,

        mergeDisabled: true,

        exportDisabled: true,

        removeDisabled: true,

        rowActionsView: 'views/email-folder/record/row-actions/default',

        actionMoveUp: function (data) {
            var model = this.collection.get(data.id);

            if (!model) {
                return;
            }

            var index = this.collection.indexOf(model);

            if (index === 0) {
                return;
            }

            Espo.Ajax.postRequest('EmailFolder/action/moveUp', {id: model.id}).then(() => {
                this.collection.fetch();
            });
        },

        actionMoveDown: function (data) {
            var model = this.collection.get(data.id);

            if (!model) {
                return;
            }

            var index = this.collection.indexOf(model);

            if ((index === this.collection.length - 1) && (this.collection.length === this.collection.total)) {
                return;
            }

            Espo.Ajax.postRequest('EmailFolder/action/moveDown', {id: model.id}).then(() => {
                this.collection.fetch();
            });
        },
    });
});
PK]ux�
�
*views/email-folder/modals/select-folder.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email-folder/modals/select-folder', ['views/modal'], function (Dep) {

    return Dep.extend({

        cssName: 'select-folder',

        template: 'email-folder/modals/select-folder',

        fitHeight: true,

        backdrop: true,

        data: function () {
            return {
                folderDataList: this.folderDataList,
            };
        },

        events: {
            'click a[data-action="selectFolder"]': function (e) {
                let $target = $(e.currentTarget);

                let id = $target.attr('data-id');
                let name = $target.attr('data-name');

                this.trigger('select', id, name);
                this.close();
            },
        },

        setup: function () {
            this.headerText = this.options.headerText || '';

            if (this.headerText === '') {
                this.buttonList.push({
                    name: 'cancel',
                    label: 'Cancel',
                });
            }

            Espo.Ui.notify(' ... ');

            this.wait(
                Espo.Ajax.getRequest('EmailFolder/action/listAll')
                    .then(data => {
                        Espo.Ui.notify(false);

                        this.folderDataList = data.list
                            .filter(item => {
                                return ['inbox', 'important', 'sent', 'drafts', 'trash'].indexOf(item.id) === -1;
                            })
                            .map(item => {
                                return {
                                    id: item.id,
                                    name: item.name,
                                    isGroup: item.id.indexOf('group:') === 0,
                                };
                            });

                        this.folderDataList.unshift({
                            id: 'inbox',
                            name: this.translate('inbox', 'presetFilters', 'Email'),
                        })
                    })
            );
        },
    });
});
PK]��?���views/email-folder/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/email-folder/list', ['views/list'], function (Dep) {

    return Dep.extend({

        quickCreate: true,

        setup: function () {
            Dep.prototype.setup.call(this);

            this.collection.data = {
                boolFilterList: ['onlyMy'],
            };
        },
    });
});
PK]j*��]]views/dashlets/memo.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import BaseDashletView from 'views/dashlets/abstract/base';

class MemoDashletView extends BaseDashletView {

    name = 'Memo'

    templateContent = `
        {{#if text}}
        <div class="complex-text complex-text-memo">{{complexText text}}</div>
        {{/if}}
    `

    data() {
        return {
            text: this.getOption('text'),
        };
    }

    afterAdding() {
        this.getContainerView().actionOptions();
    }
}

export default MemoDashletView;
PK]����views/dashlets/emails.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import RecordListDashletView from 'views/dashlets/abstract/record-list';

class EmailsDashletView extends RecordListDashletView {

    name = 'Emails'
    scope ='Emails'

    rowActionsView = 'views/email/record/row-actions/dashlet'
    listView = 'views/email/record/list-expanded'

    setupActionList() {
        if (this.getAcl().checkScope(this.scope, 'create')) {
            this.actionList.unshift({
                name: 'compose',
                text: this.translate('Compose Email', 'labels', this.scope),
                iconHtml: '<span class="fas fa-plus"></span>',
            });
        }
    }

    // noinspection JSUnusedGlobalSymbols
    actionCompose() {
        const attributes = this.getCreateAttributes() || {};

        Espo.Ui.notify(' ... ');

        const viewName = this.getMetadata().get('clientDefs.' + this.scope + '.modalViews.compose') ||
            'views/modals/compose-email';

        this.createView('modal', viewName, {
            scope: this.scope,
            attributes: attributes,
        }, view => {
            view.render();

            Espo.Ui.notify(false);

            this.listenToOnce(view, 'after:save', () => {
                this.actionRefresh();
            });
        });
    }

    /**
     * @return {module:search-manager~data}
     */
    getSearchData() {
        return {
            'advanced': [
                {
                    'attribute': 'folderId',
                    'type': 'inFolder',
                    'value': this.getOption('folder') || 'inbox',
                }
            ]
        };
    }
}

export default EmailsDashletView;
PK]"��DDviews/dashlets/options/base.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import ModalView from 'views/modal';
import Model from 'model';
import EditForModalRecordView from 'views/record/edit-for-modal';

class BaseDashletOptionsModalView extends ModalView {

    template = 'dashlets/options/base'

    cssName = 'options-modal'
    className = 'dialog dialog-record'
    name = ''
    escapeDisabled = true
    saveDisabled = false;

    buttonList = [
        {
            name: 'save',
            label: 'Apply',
            style: 'primary',
            title: 'Ctrl+Enter',
        },
        {
            name: 'cancel',
            label: 'Cancel',
            title: 'Esc',
        },
    ]

    shortcutKeys = {
        /** @this BaseDashletOptionsModalView */
        'Control+Enter': 'save',
        /** @this BaseDashletOptionsModalView */
        'Escape': function (e) {
            if (this.saveDisabled) {
                return;
            }

            e.stopPropagation();
            e.preventDefault();

            let focusedFieldView = this.getRecordView().getFocusedFieldView();

            if (focusedFieldView) {
                this.model.set(focusedFieldView.fetch(), {skipReRender: true});
            }

            if (this.getRecordView().isChanged) {
                this.confirm(this.translate('confirmLeaveOutMessage', 'messages'))
                    .then(() => this.actionClose());

                return;
            }

            this.actionClose();
        },
    }

    data() {
        return {
            options: this.optionsData,
        };
    }

    getDetailLayout() {
        let layout = this.getMetadata().get(['dashlets', this.name, 'options', 'layout']);

        if (layout) {
            return layout;
        }

        layout = [{rows: []}];

        let i = 0;
        let row = [];

        for (let field in this.fields) {
            if (!(i % 2)) {
                row = [];

                layout[0].rows.push(row);
            }

            row.push({name: field});

            i++;
        }

        return layout;
    }

    init() {
        super.init();

        this.fields = Espo.Utils.cloneDeep(this.options.fields);
        this.fieldList = Object.keys(this.fields);
        this.optionsData = this.options.optionsData;
        this.name = this.options.name;
    }

    setup() {
        this.id = 'dashlet-options';

        /** @var {module:model} */
        let model = this.model = new Model();

        model.name = 'DashletOptions';
        model.setDefs({fields: this.fields});
        model.set(this.optionsData);

        this.dataObject = {
            dashletName: this.name,
            userId: this.options.userId,
        };

        model.dashletName = this.name;
        model.userId = this.options.userId;

        this.middlePanelDefs = {};
        this.middlePanelDefsList = [];

        this.setupBeforeFinal();

        this.recordView = new EditForModalRecordView({
            model: model,
            detailLayout: this.getDetailLayout(),
            dataObject: this.dataObject,
        });

        this.assignView('record', this.recordView, '.record');

        this.$header =
            $('<span>')
                .append(
                    $('<span>').text(this.getLanguage().translate('Dashlet Options')),
                    ' &middot; ',
                    $('<span>').text(this.getLanguage().translate(this.name, 'dashlets')),
                );
    }

    setupBeforeFinal() {}

    onBackdropClick() {
        if (this.getRecordView().isChanged) {
            return;
        }

        this.close();
    }

    /**
     * @return {module:views/record/edit}
     */
    getRecordView() {
        return this.recordView;
    }

    /**
     * @return {Object|null}
     */
    fetchAttributes() {
        let attributes = this.getRecordView().fetch();

        if (this.getRecordView().validate()) {
            return null;
        }

        return attributes;
    }

    actionSave() {
        let attributes = this.fetchAttributes();

        if (attributes == null) {
            return;
        }

        this.trigger('save', attributes);
    }

    getFieldViews(withHidden) {
        if (!this.hasView('record')) {
            return {};
        }

        return this.getRecordView().getFieldViews(withHidden);
    }

    getFieldView(name) {
        return (this.getFieldViews(true) || {})[name] || null;
    }

    hideField(name, locked) {
        if (!this.getRecordView()) {
            this.whenRendered().then(() => this.hideField(name), locked);

            return;
        }

        this.getRecordView().hideField(name, locked);
    }

    showField(name) {
        if (!this.getRecordView()) {
            this.whenRendered().then(() => this.showField(name));

            return;
        }

        this.getRecordView().showField(name);
    }
}

export default BaseDashletOptionsModalView;

PK]��g��views/dashlets/stream.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import BaseDashletView from 'views/dashlets/abstract/base';

class StreamDashletView extends BaseDashletView {

    name = 'Stream'

    templateContent = '<div class="list-container">{{{list}}}</div>'

    actionRefresh() {
        if (!this.getRecordView()) {
            return;
        }

        this.getRecordView().showNewRecords();
    }

    afterRender() {
        this.getCollectionFactory().create('Note', collection => {
            this.collection = collection;

            collection.url = 'Stream';
            collection.maxSize = this.getOption('displayRecords');

            if (this.getOption('skipOwn')) {
                collection.data.skipOwn = true;
            }

            collection.fetch()
                .then(() => {
                    this.createView('list', 'views/stream/record/list', {
                        selector: '> .list-container',
                        collection: collection,
                        isUserStream: true,
                        noEdit: false,
                    }, view => {
                        view.render();
                    });
                })
        });
    }

    /**
     * @return {module:views/stream/record/list}
     */
    getRecordView() {
        return this.getView('list');
    }

    setupActionList() {
        this.actionList.unshift({
            name: 'viewList',
            text: this.translate('View'),
            iconHtml: '<span class="fas fa-align-justify"></span>',
            url: '#Stream',
        });

        if (!this.getUser().isPortal()) {
            this.actionList.unshift({
                name: 'create',
                text: this.translate('Create Post', 'labels'),
                iconHtml: '<span class="fas fa-plus"></span>',
            });
        }
    }

    // noinspection JSUnusedGlobalSymbols
    actionCreate() {
        this.createView('dialog', 'views/stream/modals/create-post', {}, view => {
            view.render();

            this.listenToOnce(view, 'after:save', () => {
                view.close();

                this.actionRefresh();
            });
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionViewList() {
        this.getRouter().navigate('#Stream', {trigger: true});
    }
}

export default StreamDashletView;
PK]�5@y@@(views/dashlets/fields/records/sort-by.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/dashlets/fields/records/sort-by', ['views/fields/enum'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            this.listenTo(this.model, 'change:entityType', () => {
                this.setupOptions();
                this.reRender();
            });
        },

        setupOptions: function () {
            var entityType = this.model.get('entityType');
            var scope = entityType;

            if (!entityType) {
                this.params.options = [];

                return;
            }

            var fieldDefs = this.getMetadata().get('entityDefs.' + scope + '.fields') || {};

            var orderableFieldList = Object.keys(fieldDefs)
                .filter(item => {
                    if (fieldDefs[item].notStorable) {
                        return false;
                    }

                    return true;
                })
                .sort((v1, v2) => {
                    return this.translate(v1, 'fields', scope).localeCompare(this.translate(v2, 'fields', scope));
                });

            var translatedOptions = {};

            orderableFieldList.forEach(item => {
                translatedOptions[item] = this.translate(item, 'fields', scope);
            });

            this.params.options = orderableFieldList;
            this.translatedOptions = translatedOptions;
        },
    });
});
PK]�����,views/dashlets/fields/records/entity-type.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/dashlets/fields/records/entity-type', ['views/fields/enum'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            this.on('change', () => {
                var o = {
                    primaryFilter: null,
                    boolFilterList: [],
                    title: this.translate('Records', 'dashlets'),
                    sortBy: null,
                    sortDirection: 'asc',
                };

                o.expandedLayout = {
                    rows: []
                };

                var entityType = this.model.get('entityType');

                if (entityType) {
                    o.title = this.translate(entityType, 'scopeNamesPlural');
                    o.sortBy = this.getMetadata().get(['entityDefs', entityType, 'collection', 'orderBy']);

                    var order = this.getMetadata().get(['entityDefs', entityType, 'collection', 'order']);

                    if (order) {
                        o.sortDirection = order;
                    } else {
                        o.sortDirection = 'asc';
                    }

                    o.expandedLayout = {
                        rows: [[{name: "name", link: true, scope: entityType}]]
                    };
                }

                this.model.set(o);
            });
        },

        setupOptions: function () {
            this.params.options =  Object.keys(this.getMetadata().get('scopes'))
                .filter(scope => {
                    if (this.getMetadata().get('scopes.' + scope + '.disabled')) {
                        return;
                    }

                    if (!this.getAcl().checkScope(scope, 'read')) {
                        return;
                    }

                    if (!this.getMetadata().get(['scopes', scope, 'entity'])) {
                        return;
                    }

                    if (!this.getMetadata().get(['scopes', scope, 'object'])) {
                        return;
                    }

                    return true;
                })
                .sort((v1, v2) => {
                    return this.translate(v1, 'scopeNames').localeCompare(this.translate(v2, 'scopeNames'));
                });

            this.params.options.unshift('');
        },
    });
});
PK]a�9���/views/dashlets/fields/records/primary-filter.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/dashlets/fields/records/primary-filter', ['views/fields/enum'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            this.listenTo(this.model, 'change:entityType', () => {
                this.setupOptions();
                this.reRender();
            });
        },

        setupOptions: function () {
            var entityType = this.model.get('entityType');

            if (!entityType) {
                this.params.options = [];

                return;
            }

            var filterList = this.getMetadata().get(['clientDefs', entityType, 'filterList']) || [];
            this.params.options = [];

            filterList.forEach(item => {
                if (typeof item === 'object' && item.name) {
                    if (item.accessDataList) {
                        if (
                            !Espo.Utils
                                .checkAccessDataList(item.accessDataList, this.getAcl(), this.getUser(), null, true)
                        ) {
                            return false;
                        }
                    }

                    this.params.options.push(item.name);

                    return;
                }

                this.params.options.push(item);
            });

            this.params.options.unshift('all');

            this.translatedOptions = {};

            this.params.options.forEach(item => {
                this.translatedOptions[item] = this.translate(item, 'presetFilters', entityType);
            });
        },
    });
});
PK]��70views/dashlets/fields/records/expanded-layout.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import BaseFieldView from 'views/fields/base';
import MultiSelect from 'ui/multi-select';

class ExpandedLayoutDashletFieldView extends BaseFieldView {

    listTemplate = 'dashlets/fields/records/expanded-layout/edit'
    detailTemplate = 'dashlets/fields/records/expanded-layout/edit'
    editTemplate ='dashlets/fields/records/expanded-layout/edit'

    delimiter = ':,:'

    getRowHtml(row, i) {
        row = row || [];

        let list = [];

        row.forEach(item => {
            list.push(item.name);
        });

        return $('<div>')
            .append(
                $('<input>')
                    .attr('type', 'text')
                    .addClass('row-' + i.toString())
                    .attr('value', list.join(this.delimiter))
            )
            .get(0).outerHTML;
    }

    afterRender() {
        this.$container = this.$el.find('>.layout-container');

        let rowList = (this.model.get(this.name) || {}).rows || [];

        rowList = Espo.Utils.cloneDeep(rowList);

        rowList.push([]);

        let fieldDataList = this.getFieldDataList();

        rowList.forEach((row, i) => {
            let rowHtml = this.getRowHtml(row, i);
            let $row = $(rowHtml);

            this.$container.append($row);

            let $input = $row.find('input');

            /** @type {module:ui/multi-select~Options} */
            let multiSelectOptions = {
                items: fieldDataList,
                delimiter: this.delimiter,
                matchAnyWord: this.matchAnyWord,
                draggable: true,
            };

            MultiSelect.init($input, multiSelectOptions);

            $input.on('change', () => {
                this.trigger('change');
                this.reRender();
            });
        });
    }

    getFieldDataList() {
        const scope = this.model.get('entityType') ||
            this.getMetadata().get(['dashlets', this.dataObject.dashletName, 'entityType']);

        if (!scope) {
            return [];
        }

        let fields = this.getMetadata().get(['entityDefs', scope, 'fields']) || {};

        let forbiddenFieldList = this.getAcl().getScopeForbiddenFieldList(scope);

        let fieldList = Object.keys(fields)
            .sort((v1, v2) => {
                 return this.translate(v1, 'fields', scope)
                     .localeCompare(this.translate(v2, 'fields', scope));
            })
            .filter(item => {
                if (
                    fields[item].disabled ||
                    fields[item].listLayoutDisabled ||
                    fields[item].utility
                ) {
                    return false;
                }

                if (
                    fields[item].layoutAvailabilityList &&
                    !fields[item].layoutAvailabilityList.includes('list')
                ) {
                    return false;
                }

                if (forbiddenFieldList.indexOf(item) !== -1) {
                    return false;
                }

                return true;
            });

        let dataList = [];

        fieldList.forEach(item => {
            dataList.push({
                value: item,
                text: this.translate(item, 'fields', scope),
            });
        });

        return dataList;
    }

    fetch() {
        var value = {
            rows: [],
        };

        this.$el.find('input').each((i, el) => {
            let row = [];
            let list = ($(el).val() || '').split(this.delimiter);

            if (list.length === 1 && list[0] === '') {
                list = [];
            }

            if (list.length === 0) {
                return;
            }

            list.forEach(item => {
                let o = {name: item};

                if (item === 'name') {
                    o.link = true;
                }

                row.push(o);
            });

            value.rows.push(row);
        });

        let data = {};

        data[this.name] = value;

        return data;
    }
}

export default ExpandedLayoutDashletFieldView;
PK]������/views/dashlets/fields/records/sort-direction.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/dashlets/fields/records/sort-direction', ['views/fields/enum'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            this.listenTo(this.model, 'change:entityType', () => {
                this.setupOptions();
                this.reRender();
            });
        },
    });
});
PK]��դ��1views/dashlets/fields/records/bool-filter-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/dashlets/fields/records/bool-filter-list', ['views/fields/multi-enum'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            this.listenTo(this.model, 'change:entityType', () => {
                this.setupOptions();
                this.reRender();
            });
        },

        setupOptions: function () {
            var entityType = this.model.get('entityType');

            if (!entityType) {
                this.params.options = [];

                return;
            }

            var filterList = this.getMetadata().get(['clientDefs', entityType, 'boolFilterList']) || [];

            this.params.options = [];

            filterList.forEach(item => {
                if (typeof item === 'object' && item.name) {
                    if (item.accessDataList) {
                        if (
                            !Espo.Utils
                                .checkAccessDataList(item.accessDataList, this.getAcl(), this.getUser(), null, true)
                        ) {
                            return false;
                        }
                    }

                    this.params.options.push(item.name);
                    return;
                }

                this.params.options.push(item);
            });

            if (
                this.getMetadata().get(['scopes', entityType, 'stream']) &&
                this.getAcl().checkScope(entityType, 'stream')
            ) {
                this.params.options.push('followed');
            }

            this.translatedOptions = {};

            this.params.options.forEach(item => {
                this.translatedOptions[item] = this.translate(item, 'boolFilters', entityType);
            });
        },
    });
});
PK]�����	�	&views/dashlets/fields/emails/folder.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import EnumFieldView from 'views/fields/enum';

class EmailFolderDashletFieldView extends EnumFieldView {

    /** @type {{id: string, name: string}[]} */
    folderDataList

    setup() {
        super.setup();

        let userId = this.dataObject.userId ?? this.getUser().id;

        this.wait(
            Espo.Ajax.getRequest('EmailFolder/action/listAll', {userId: userId})
                .then(data => this.folderDataList = data.list)
                .then(() => this.setupOptions())
        );

        this.setupOptions();
    }

    setupOptions() {
        if (!this.folderDataList) {
            return;
        }

        this.params.options = this.folderDataList
            .map(item => item.id)
            .filter(item => item !== 'inbox' && item !== 'trash');

        this.params.options.unshift('');

        this.translatedOptions = {'': this.translate('inbox', 'presetFilters', 'Email')};

        this.folderDataList.forEach(item => {
            this.translatedOptions[item.id] = item.name;
        });
    }
}

export default EmailFolderDashletFieldView;
PK]m�cSRRviews/dashlets/iframe.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import BaseDashletView from 'views/dashlets/abstract/base';

class IframeDashletView extends BaseDashletView {

    name = 'Iframe'

    templateContent = '<iframe style="margin: 0; border: 0;"></iframe>'

    afterRender() {
        const $iframe = this.$el.find('iframe');

        const url = this.getOption('url');

        if (url) {
            $iframe.attr('src', url);
        }

        this.$el.addClass('no-padding');
        this.$el.css('overflow', 'hidden');

        const height = this.$el.height();

        $iframe.css('height', height);
        $iframe.css('width', '100%');
    }

    afterAdding() {
        this.getContainerView().actionOptions();
    }
}

export default IframeDashletView;
PK]6��j
j
views/dashlets/records.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import RecordListDashletView from 'views/dashlets/abstract/record-list';

class RecordsDashletView extends RecordListDashletView {

    name = 'Records'

    rowActionsView = 'views/record/row-actions/view-and-edit'
    listView = 'views/email/record/list-expanded'

    init() {
        super.init();

        this.scope = this.getOption('entityType');
    }

    getSearchData() {
        const data = {
            primary: /** @type string */this.getOption('primaryFilter'),
        };

        if (data.primary === 'all') {
            delete data.primary;
        }

        const bool = {};

        (this.getOption('boolFilterList') || []).forEach(item => {
            bool[item] = true;
        });

        data.bool = bool;

        return data;
    }

    setupActionList() {
        const scope = this.getOption('entityType');

        if (scope && this.getAcl().checkScope(scope, 'create')) {
            this.actionList.unshift({
                name: 'create',
                text: this.translate('Create ' + scope, 'labels', scope),
                iconHtml: '<span class="fas fa-plus"></span>',
                url: '#' + scope + '/create',
            });
        }
    }
}

export default RecordsDashletView;
PK]i�&views/dashlets/abstract/record-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import BaseDashletView from 'views/dashlets/abstract/base';
import SearchManager from 'search-manager';

class RecordListDashletView extends BaseDashletView {

    templateContent = '<div class="list-container">{{{list}}}</div>'

    /**
     * A scope.
     * @type {string}
     */
    scope

    listView = null
    listViewColumn = 'views/record/list'
    listViewExpanded = 'views/record/list-expanded'
    layoutType = 'expanded'

    optionsFields = {
        title: {
            type: 'varchar',
            required: true,
        },
        autorefreshInterval: {
            type: 'enumFloat',
            options: [0, 0.5, 1, 2, 5, 10],
        },
        displayRecords: {
            type: 'enumInt',
            options: [3, 4, 5, 10, 15],
        },
    }

    rowActionsView = 'views/record/row-actions/view-and-edit'

    init() {
        super.init();

        this.scope = this.getMetadata().get(['dashlets', this.name, 'entityType']) || this.scope;
    }

    checkAccess() {
        return this.getAcl().check(this.scope, 'read');
    }

    /**
     * @return {module:search-manager~data}
     */
    getSearchData() {
        return this.getOption('searchData');
    }

    afterRender() {
        this.getCollectionFactory().create(this.scope, collection => {
            const searchData = this.getSearchData();

            this.searchManager = new SearchManager(collection, 'list', null, this.getDateTime(), searchData);

            if (!this.scope) {
                this.$el.find('.list-container')
                    .html(this.translate('selectEntityType', 'messages', 'DashletOptions'));

                return;
            }

            if (!this.checkAccess()) {
                this.$el.find('.list-container').html(this.translate('No Access'));

                return;
            }

            if (this.collectionUrl) {
                collection.url = this.collectionUrl;
            }

            this.collection = collection;

            collection.orderBy = this.getOption('orderBy') || this.getOption('sortBy') || this.collection.orderBy;

            if (this.getOption('orderBy')) {
                collection.order = 'asc';
            }

            if (this.hasOption('asc')) {
                collection.order = this.getOption('asc') ? 'asc' : false;
            }

            if (this.getOption('sortDirection') === 'asc') {
                collection.order = 'asc';
            } else if (this.getOption('sortDirection') === 'desc') {
                collection.order = 'desc';
            }

            if (this.getOption('order') === 'asc') {
                collection.order = 'asc';
            }
            else if (this.getOption('order') === 'desc') {
                collection.order = 'desc';
            }

            collection.maxSize = this.getOption('displayRecords');
            collection.where = this.searchManager.getWhere();

            const viewName = this.listView || ((this.layoutType === 'expanded') ?
                this.listViewExpanded : this.listViewColumn);

            this.createView('list', viewName, {
                collection: collection,
                selector: '.list-container',
                pagination: this.getOption('pagination') ? 'bottom' : false,
                type: 'listDashlet',
                rowActionsView: this.rowActionsView,
                checkboxes: false,
                showMore: true,
                listLayout: this.getOption(this.layoutType + 'Layout'),
                skipBuildRows: true,
            }, (view) => {
                view.getSelectAttributeList(selectAttributeList => {
                    if (selectAttributeList) {
                        collection.data.select = selectAttributeList.join(',');
                    }

                    collection.fetch();
                });
            });
        });
    }

    setupActionList() {
        if (this.scope && this.getAcl().checkScope(this.scope, 'create')) {
            this.actionList.unshift({
                name: 'create',
                text: this.translate('Create ' + this.scope, 'labels', this.scope),
                iconHtml: '<span class="fas fa-plus"></span>',
                url: '#'+this.scope+'/create',
            });
        }
    }

    actionRefresh() {
        if (!this.collection) {
            return;
        }

        this.collection.where = this.searchManager.getWhere();
        this.collection.fetch({
            previousDataList: this.collection.models.map(model => {
                return Espo.Utils.cloneDeep(model.attributes);
            }),
        });
    }

    // noinspection JSUnusedGlobalSymbols
    actionCreate() {
        const attributes = this.getCreateAttributes() || {};

        if (this.getOption('populateAssignedUser')) {
            if (this.getMetadata().get(['entityDefs', this.scope, 'fields', 'assignedUsers'])) {
                attributes['assignedUsersIds'] = [this.getUser().id];
                attributes['assignedUsersNames'] = {};
                attributes['assignedUsersNames'][this.getUser().id] = this.getUser().get('name');
            } else {
                attributes['assignedUserId'] = this.getUser().id;
                attributes['assignedUserName'] = this.getUser().get('name');
            }
        }

        Espo.Ui.notify(' ... ');

        const viewName = this.getMetadata().get('clientDefs.' + this.scope + '.modalViews.edit') ||
            'views/modals/edit';

        this.createView('modal', viewName, {
            scope: this.scope,
            attributes: attributes,
        }, view => {
            view.render();
            view.notify(false);

            this.listenToOnce(view, 'after:save', () => {
                this.actionRefresh();
            });
        });
    }

    getCreateAttributes() {}
}

export default RecordListDashletView;
PK]�݆���views/dashlets/abstract/base.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/dashlets/abstract/base */

import View from 'view';

/**
 * A base dashlet view. All dashlets should extend it.
 */
class BaseDashletView extends View {

    /** @type {Object.<string, *>|null}*/
    optionsData = null

    optionsFields = {
        title: {
            type: 'varchar',
            required: true,
        },
        autorefreshInterval: {
            type: 'enumFloat',
            options: [0, 0.5, 1, 2, 5, 10],
        },
    }

    disabledForReadOnlyActionList = ['options', 'remove']
    disabledForLockedActionList = ['remove']

    noPadding = false

    /**
     * A button. Handled by an `action{Name}` method or a click handler.
     *
     * @typedef module:views/dashlets/abstract/base~button
     *
     * @property {string} name A name.
     * @property {string} [label] A label.
     * @property {string} [html] An HTML.
     * @property {string} [text] A text.
     * @property {string} [title] A title (not translatable).
     * @property {function()} [onClick] A click handler.
     */

    /**
     * A dropdown action. Handled by an `action{Name}` method or a click handler.
     *
     * @typedef module:views/dashlets/abstract/base~action
     *
     * @property {string} name A name.
     * @property {string} [label] A label.
     * @property {string} [html] An HTML.
     * @property {string} [text] A text.
     * @property {string} [title] A title (not translatable).
     * @property {string} [iconHtml] An icon HTML.
     * @property {string} [url] A link URL.
     * @property {function()} [onClick] A click handler.
     */

    /**
     * Buttons.
     *
     * @protected
     * @type {Array<module:views/dashlets/abstract/base~button>}
     */
    buttonList = []

    /**
     * Dropdown actions.
     *
     * @protected
     * @type {Array<module:views/dashlets/abstract/base~action>}
     */
    actionList = [
        {
            name: 'refresh',
            label: 'Refresh',
            iconHtml: '<span class="fas fa-sync-alt"></span>',
        },
        {
            name: 'options',
            label: 'Options',
            iconHtml: '<span class="fas fa-pencil-alt"></span>',
        },
        {
            name: 'remove',
            label: 'Remove',
            iconHtml: '<span class="fas fa-times"></span>',
        },
    ]

    /**
     * Refresh.
     */
    actionRefresh() {
        this.render();
    }

    /**
     * Show options.
     */
    actionOptions() {}

    init() {
        this.name = this.options.name || this.name;
        this.id = this.options.id;

        this.defaultOptions = this.getMetadata().get(['dashlets', this.name, 'options', 'defaults']) ||
            this.defaultOptions || {};

        this.defaultOptions = {
            title: this.getLanguage().translate(this.name, 'dashlets'),
            ...this.defaultOptions
        };

        this.defaultOptions = Espo.Utils.clone(this.defaultOptions);

        this.optionsFields = this.getMetadata().get(['dashlets', this.name, 'options', 'fields']) ||
            this.optionsFields || {};

        this.optionsFields = Espo.Utils.clone(this.optionsFields);

        this.setupDefaultOptions();

        let options = Espo.Utils.cloneDeep(this.defaultOptions);

        for (let key in options) {
            if (typeof options[key] == 'function') {
                options[key] = options[key].call(this);
            }
        }

        let storedOptions;

        if (!this.options.readOnly) {
            storedOptions = this.getPreferences().getDashletOptions(this.id) || {};
        }
        else {
            let allOptions = this.getConfig().get('forcedDashletsOptions') ||
                this.getConfig().get('dashletsOptions') || {};

            storedOptions = allOptions[this.id] || {};
        }

        this.optionsData = _.extend(options, storedOptions);

        if (this.optionsData.autorefreshInterval) {
            let interval = this.optionsData.autorefreshInterval * 60000;

            let t;

            let process = () => {
                t = setTimeout(() => {
                    this.actionRefresh();

                    process();
                }, interval);
            };

            process();

            this.once('remove', () => {
                clearTimeout(t);
            });
        }

        this.actionList = Espo.Utils.clone(this.actionList);
        this.buttonList = Espo.Utils.clone(this.buttonList);

        if (this.options.readOnly) {
            this.actionList = this.actionList.filter(item => {
                if (~this.disabledForReadOnlyActionList.indexOf(item.name)) {
                    return false;
                }

                return true;
            })
        }

        if (this.options.locked) {
            this.actionList = this.actionList
                .filter(item => !this.disabledForLockedActionList.includes(item.name));
        }

        this.setupActionList();
        this.setupButtonList();
    }

    /**
     * Set up default options.
     */
    setupDefaultOptions() {}

    /**
     * Set up actions.
     */
    setupActionList() {}

    /**
     * Set up buttons.
     */
    setupButtonList() {}

    /**
     * Has an option.
     *
     * @param {string} key
     * @return {boolean}
     */
    hasOption(key) {
        return key in this.optionsData;
    }

    /**
     * Get an option value.
     *
     * @param {string} key
     * @return {*}
     */
    getOption(key) {
        return this.optionsData[key];
    }

    /**
     * Get a title.
     * @return {string|null}
     */
    getTitle() {
        let title = this.getOption('title');

        if (!title) {
            title = null;
        }

        return title;
    }

    /**
     * @return {module:views/dashlet}
     */
    getContainerView() {
        return /** @type module:views/dashlet */this.getParentView();
    }

    /**
     * @internal
     * @param {MouseEvent} event
     * @param {HTMLElement} element
     */
    handleAction(event, element) {
        Espo.Utils.handleAction(this, event, element, {
            actionItems: [...this.buttonList, ...this.actionList],
            className: 'dashlet-action',
        });
    }
}

export default BaseDashletView;
PK]���mmviews/dashlet.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/dashlet */

import View from 'view'

/**
 * A dashlet container view.
 */
class DashletView extends View {

    /** @inheritDoc */
    template = 'dashlet'

    /**
     * A dashlet name.
     *
     * @type {string}
     */
    name

    /**
     * A dashlet ID.
     *
     * @type {string}
     */
    id

    /**
     * An options view name.
     *
     * @protected
     * @type {string|null}
     */
    optionsView = null

    /** @inheritDoc */
    data() {
        return {
            name: this.name,
            id: this.id,
            title: this.getTitle(),
            actionList: (this.getBodyView() || {}).actionList || [],
            buttonList: (this.getBodyView() || {}).buttonList || [],
            noPadding: (this.getBodyView() || {}).noPadding,
        };
    }

    /** @inheritDoc */
    events = {
        /** @this DashletView */
        'click .action': function (e) {
            const isHandled = Espo.Utils.handleAction(this, e.originalEvent, e.currentTarget);

            if (isHandled) {
                return;
            }

            this.getBodyView().handleAction(e.originalEvent, e.currentTarget);
        },
        /** @this DashletView */
        'mousedown .panel-heading .dropdown-menu': function (e) {
            // Prevent dragging.
            e.stopPropagation();
        },
        /** @this DashletView */
        'shown.bs.dropdown .panel-heading .btn-group': function (e) {
            this.controlDropdownShown($(e.currentTarget).parent());
        },
        /** @this DashletView */
        'hide.bs.dropdown .panel-heading .btn-group': function () {
            this.controlDropdownHide();
        },
    }

    controlDropdownShown($dropdownContainer) {
        let $panel = this.$el.children().first();

        let dropdownBottom = $dropdownContainer.find('.dropdown-menu')
            .get(0).getBoundingClientRect().bottom;

        let panelBottom = $panel.get(0).getBoundingClientRect().bottom;

        if (dropdownBottom < panelBottom) {
            return;
        }

        $panel.addClass('has-dropdown-opened');
    }

    controlDropdownHide() {
        this.$el.children().first().removeClass('has-dropdown-opened');
    }

    /** @inheritDoc */
    setup() {
        this.name = this.options.name;
        this.id = this.options.id;

        this.on('resize', () => {
            let bodyView = this.getView('body');

            if (!bodyView) {
                return;
            }

            bodyView.trigger('resize');
        });

        let viewName = this.getMetadata().get(['dashlets', this.name, 'view']) ||
            'views/dashlets/' + Espo.Utils.camelCaseToHyphen(this.name);

        this.createView('body', viewName, {
            selector: '.dashlet-body',
            id: this.id,
            name: this.name,
            readOnly: this.options.readOnly,
            locked: this.options.locked,
        });
    }

    /**
     * Refresh.
     */
    refresh() {
        this.getBodyView().actionRefresh();
    }

    actionRefresh() {
        this.refresh();
    }

    actionOptions() {
        let optionsView =
            this.getMetadata().get(['dashlets', this.name, 'options', 'view']) ||
            this.optionsView ||
            'views/dashlets/options/base';

        Espo.Ui.notify(' ... ');

        this.createView('options', optionsView, {
            name: this.name,
            optionsData: this.getOptionsData(),
            fields: this.getBodyView().optionsFields,
        }, view => {
            view.render();

            Espo.Ui.notify(false);

            this.listenToOnce(view, 'save', (attributes) => {
                let id = this.id;

                Espo.Ui.notify(this.translate('saving', 'messages'));

                this.getPreferences().once('sync', () => {
                    this.getPreferences().trigger('update');

                    Espo.Ui.notify(false);

                    view.close();
                    this.trigger('change');
                });

                let o = this.getPreferences().get('dashletsOptions') || {};

                o[id] = attributes;

                this.getPreferences().save({dashletsOptions: o}, {patch: true});
            });
        });
    }

    /**
     * Get options data.
     *
     * @returns {Object}
     */
    getOptionsData() {
        return this.getBodyView().optionsData;
    }

    /**
     * Get an option value.
     *
     * @param {string} key A option name.
     * @returns {*}
     */
    getOption(key) {
        return this.getBodyView().getOption(key);
    }

    /**
     * Get a dashlet title.
     *
     * @returns {string}
     */
    getTitle() {
        return this.getBodyView().getTitle();
    }

    /**
     * @return {module:views/dashlets/abstract/base}
     */
    getBodyView() {
        return this.getView('body');
    }

    // noinspection JSUnusedGlobalSymbols
    actionRemove() {
        this.confirm(this.translate('confirmation', 'messages'), () => {
            this.trigger('remove-dashlet');
            this.$el.remove();
            this.remove();
        });
    }
}

export default DashletView;
PK]hrC�

views/merge.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import MainView from 'views/main';

class MergeView extends MainView {

    template = 'merge'

    name = 'Merge'

    headerView = 'views/header'
    recordView = 'views/record/merge'

    setup() {
        this.models = this.options.models;

        this.setupHeader();
        this.setupRecord();
    }

    setupHeader() {
        this.createView('header', this.headerView, {
            model: this.model,
            fullSelector: '#main > .page-header'
        });
    }

    setupRecord() {
        this.createView('body', this.recordView, {
            fullSelector: '#main > .body',
            models: this.models,
            collection: this.collection
        });
    }

    getHeader() {
        return this.buildHeaderHtml([
            $('<a>')
                .attr('href', '#' + this.models[0].entityType)
                .text(this.getLanguage().translate(this.models[0].entityType, 'scopeNamesPlural')),
            $('<span>')
                .text(this.getLanguage().translate('Merge'))
        ]);
    }

    updatePageTitle() {
        this.setPageTitle(this.getLanguage().translate('Merge'));
    }
}

export default MergeView;
PK]I�ww views/last-viewed/record/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/last-viewed/record/list', ['views/record/list'], function (Dep) {

    return Dep.extend({

        layoutName: 'listForLastViewed',

        rowActionsDisabled: true,
        massActionsDisabled: true,
        headerDisabled: true,
    });
});
PK]<�����views/last-viewed/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/last-viewed/list', ['views/list'], function (Dep) {

    return Dep.extend({

        searchPanel: false,

        createButton: false,

        setup: function () {
            Dep.prototype.setup.call(this);

            this.collection.url = 'LastViewed';
        },
    });
});
PK]��3�
�
views/note/fields/users.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/note/fields/users', ['views/fields/link-multiple'], function (Dep) {

    return Dep.extend({

        init: function () {
            this.messagePermission = this.getAcl().getPermissionLevel('message');
            this.portalPermission = this.getAcl().getPermissionLevel('portal');

            if (this.messagePermission === 'no' && this.portalPermission === 'no') {
                this.readOnly = true;
            }

            Dep.prototype.init.call(this);
        },

        getSelectBoolFilterList: function () {
            if (this.messagePermission === 'team') {
                return ['onlyMyTeam'];
            }

            if (this.portalPermission === 'yes') {
                return null;
            }
        },

        getSelectPrimaryFilterName: function () {
            if (this.portalPermission === 'yes' && this.messagePermission === 'no') {
                return 'activePortal';
            }

            return 'active';
        },

        getSelectFilterList: function () {

            if (this.portalPermission === 'yes') {
                if (this.messagePermission === 'no') {
                     return ['activePortal'];
                }

                return ['active', 'activePortal'];
            }

            return null;
        },

    });
});
PK]��(�(views/note/fields/post.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/note/fields/post', ['views/fields/text', 'lib!jquery-textcomplete'], function (Dep, Textcomplete) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            this.events['paste textarea'] = e => this.handlePaste(e);

            this.insertedImagesData = {};
        },

        handlePaste: function (e) {
            if (!e.originalEvent.clipboardData) {
                return;
            }

            let text = e.originalEvent.clipboardData.getData('text/plain');

            if (!text) {
                return;
            }

            text = text.trim();

            if (!text) {
                return;
            }

            this.handlePastedText(text, e.originalEvent);
        },

        afterRenderEdit: function () {
            let placeholderText = this.options.placeholderText ||
                this.translate('writeMessage', 'messages', 'Note');

            this.$element.attr('placeholder', placeholderText);

            this.$textarea = this.$element;

            let $textarea = this.$textarea;

            $textarea.off('drop');
            $textarea.off('dragover');
            $textarea.off('dragleave');
            $textarea.off('paste');

            $textarea.on('paste', (e) => {
                var items = e.originalEvent.clipboardData.items;

                if (items) {
                    for (var i = 0; i < items.length; i++) {
                        if (!~items[i].type.indexOf('image')) {
                            continue;
                        }

                        var blob = items[i].getAsFile();

                        this.trigger('add-files', [blob]);
                    }
                }
            });

            this.$textarea.on('drop', (e) => {
                e.preventDefault();
                e.stopPropagation();

                e = e.originalEvent;

                if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length) {
                    this.trigger('add-files', e.dataTransfer.files);
                }

                this.$textarea.attr('placeholder', originalPlaceholderText);
            });

            let originalPlaceholderText = this.$textarea.attr('placeholder');

            this.$textarea.on('dragover', e => {
                e.preventDefault();

                this.$textarea.attr('placeholder', this.translate('dropToAttach', 'messages'));
            });

            this.$textarea.on('dragleave', e => {
                e.preventDefault();

                this.$textarea.attr('placeholder', originalPlaceholderText);
            });

            let assignmentPermission = this.getAcl().get('assignmentPermission');

            var buildUserListUrl = term => {
                let url = 'User?q=' + term + '&' + $.param({'primaryFilter': 'active'}) +
                    '&orderBy=name&maxSize=' + this.getConfig().get('recordsPerPage') +
                    '&select=id,name,userName';

                if (assignmentPermission === 'team') {
                    url += '&' + $.param({'boolFilterList': ['onlyMyTeam']})
                }

                return url;
            };

            if (assignmentPermission !== 'no' && this.model.isNew()) {
                this.$element.textcomplete([{
                    match: /(^|\s)@(\w*)$/,
                    search: (term, callback) => {
                        if (term.length === 0) {
                            callback([]);

                            return;
                        }

                        Espo.Ajax
                            .getRequest(buildUserListUrl(term))
                            .then(data => {
                                callback(data.list)
                            });
                    },
                    template: mention => {
                        return this.getHelper().escapeString(mention.name) +
                            ' <span class="text-muted">@' +
                            this.getHelper().escapeString(mention.userName) + '</span>';
                    },
                    replace: o => {
                        return '$1@' + o.userName + '';
                    },
                }],{zIndex: 1100});

                this.once('remove', () => {
                    if (this.$element.length) {
                        this.$element.textcomplete('destroy');
                    }
                });
            }
        },

        validateRequired: function () {
            if (this.isRequired()) {
                if ((this.model.get('attachmentsIds') || []).length) {
                    return false;
                }
            }

            return Dep.prototype.validateRequired.call(this);
        },

        handlePastedText: function (text, event) {
            if (!(/^http(s){0,1}\:\/\//.test(text))) {
                return;
            }

            let imageExtensionList = ['jpg', 'jpeg', 'png', 'gif'];
            let regExpString = '.+\\.(' + imageExtensionList.join('|') + ')(/?.*){0,1}$';
            let regExp = new RegExp(regExpString, 'i');
            let url = text;
            let siteUrl = this.getConfig().get('siteUrl').replace(/\/$/, '');

            let attachmentIdList = this.model.get('attachmentsIds') || [];

            if (regExp.test(text)) {
                let insertedId = this.insertedImagesData[url];

                if (insertedId) {
                    if (~attachmentIdList.indexOf(insertedId)) {
                        return;
                    }
                }

                Espo.Ajax
                    .postRequest('Attachment/fromImageUrl', {
                        url: url,
                        parentType: 'Note',
                        field: 'attachments',
                    })
                    .then(attachment => {
                        let attachmentIdList = Espo.Utils.clone(this.model.get('attachmentsIds') || []);
                        let attachmentNames = Espo.Utils.clone(this.model.get('attachmentsNames') || {});
                        let attachmentTypes = Espo.Utils.clone(this.model.get('attachmentsTypes') || {});

                        attachmentIdList.push(attachment.id);
                        attachmentNames[attachment.id] = attachment.name;
                        attachmentTypes[attachment.id] = attachment.type;

                        this.insertedImagesData[url] = attachment.id;

                        this.model.set({
                            attachmentsIds: attachmentIdList,
                            attachmentsNames: attachmentNames,
                            attachmentsTypes: attachmentTypes,
                        });
                    })
                    .catch(xhr => {
                        xhr.errorIsHandled = true;
                    });

                return;
            }

            if (/\?entryPoint\=image\&/.test(text) && text.indexOf(siteUrl) === 0) {
                url = text.replace(/[\&]{0,1}size\=[a-z\-]*/, '');

                let match = /\&{0,1}id\=([a-z0-9A-Z]*)/g.exec(text)

                if (match.length !== 2) {
                    return;
                }

                let id = match[1];

                if (~attachmentIdList.indexOf(id)) {
                    return;
                }

                let insertedId = this.insertedImagesData[id];

                if (insertedId) {
                    if (~attachmentIdList.indexOf(insertedId)) {
                        return;
                    }
                }

                Espo.Ajax
                    .postRequest('Attachment/copy/' + id, {
                        parentType: 'Note',
                        field: 'attachments',
                    })
                    .then(attachment => {
                        let attachmentIdList = Espo.Utils.clone(this.model.get('attachmentsIds') || []);
                        let attachmentNames = Espo.Utils.clone(this.model.get('attachmentsNames') || {});
                        let attachmentTypes = Espo.Utils.clone(this.model.get('attachmentsTypes') || {});

                        attachmentIdList.push(attachment.id);
                        attachmentNames[attachment.id] = attachment.name;
                        attachmentTypes[attachment.id] = attachment.type;

                        this.insertedImagesData[id] = attachment.id;

                        this.model.set({
                            attachmentsIds: attachmentIdList,
                            attachmentsNames: attachmentNames,
                            attachmentsTypes: attachmentTypes,
                        });
                    })
                    .catch(xhr => {
                        xhr.errorIsHandled = true;
                    });
            }
        },
    });
});
PK]>��P~~views/note/detail.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/note/detail', ['views/main'], (Dep) => {

    /**
     * @class
     * @name Class
     * @extends module:views/main
     * @memberOf module:views/note/detail
     */
    return Dep.extend(/** @lends module:views/note/detail.Class# */{

        templateContent: `
            <div class="header page-header">{{{header}}}</div>
            <div class="record list-container list-container-panel block-center">{{{record}}}</div>
        `,

        /**
         * @private
         */
        isDeleted: false,

        setup: function () {
            this.scope = this.model.entityType;

            this.setupHeader();
            this.setupRecord();

            this.listenToOnce(this.model, 'remove', () => {
                this.clearView('record');
                this.isDeleted = true;
                this.getHeaderView().reRender();
            });
        },

        setupHeader: function () {
            this.createView('header', 'views/header', {
                selector: '> .header',
                scope: this.scope,
                fontSizeFlexible: true,
            });
        },

        setupRecord: function () {
            this.wait(
                this.getCollectionFactory().create(this.scope)
                    .then(collection => {
                        this.collection = collection;
                        this.collection.add(this.model);

                        this.createView('record', 'views/stream/record/list', {
                            selector: '> .record',
                            collection: this.collection,
                            isUserStream: true,
                        });
                    })
            );
        },

        getHeader: function () {
            let parentType = this.model.get('parentType');
            let parentId = this.model.get('parentId');
            let parentName = this.model.get('parentName');
            let type = this.model.get('type');

            let $type = $('<span>')
                    .text(this.getLanguage().translateOption(type, 'type', 'Note'));

            if (this.model.get('deleted') || this.isDeleted) {
                $type.css('text-decoration', 'line-through');
            }

            if (parentType && parentId) {
                return this.buildHeaderHtml([
                    $('<a>')
                        .attr('href', '#' + parentType)
                        .text(this.translate(parentType, 'scopeNamesPlural')),
                    $('<a>')
                        .attr('href', '#' + parentType + '/view/' + parentId)
                        .text(parentName || parentId),
                    $('<span>')
                        .text(this.translate('Stream', 'scopeNames')),
                    $type,
                ]);
            }

            return this.buildHeaderHtml([
                $('<span>')
                    .text(this.translate('Stream', 'scopeNames')),
                $type,
            ]);
        },
    });
});
PK]5`�ێ�views/note/record/edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/note/record/edit', ['views/record/edit'], function (Dep) {

    return Dep.extend({

        sideView: null,

        isWide: true,

        setup: function () {
            Dep.prototype.setup.call(this);

            this.controlRequiredFields();

            this.listenTo(this.model, 'change:attachmentsIds', () => {
                this.controlRequiredFields();
            });
        },

        controlRequiredFields: function () {
            if (!(this.model.get('attachmentsIds') || []).length) {
                this.setFieldRequired('post');
            } else {
                this.setFieldNotRequired('post');
            }
        },

        afterRender: function () {
            Dep.prototype.afterRender.call(this);
        },
    });
});
PK]�N�		views/note/modals/edit.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/note/modals/edit', ['views/modals/edit'], function (Dep) {

    return Dep.extend({

        fullFormDisabled: true,

        setup: function () {
            Dep.prototype.setup.call(this);

            this.once('ready', () => {
                let recordView = this.getView('edit') || this.getView('record');

                if (recordView) {
                    var fieldView = recordView.getFieldView('post');

                    if (fieldView) {
                        this.listenTo(fieldView, 'add-files', files => {
                            var attachmentsView = recordView.getFieldView('attachments');

                            if (attachmentsView) {
                                recordView.getFieldView('attachments').uploadFiles(files);
                            }
                        });
                    }
                }
            });
        },
    });
});
PK]��s��views/search/filter.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module views/search/filter */

import View from 'view';

class FilterView extends View {

    template = 'search/filter'

    data() {
        return {
            name: this.name,
            scope: this.model.entityType,
            notRemovable: this.options.notRemovable,
        };
    }

    setup() {
        let name = this.name = this.options.name;
        let type = this.model.getFieldType(name);

        if (type) {
            let viewName = this.model.getFieldParam(name, 'view') ||
                this.getFieldManager().getViewName(type);

            this.createView('field', viewName, {
                mode: 'search',
                model: this.model,
                selector: '.field',
                defs: {
                    name: name,
                },
                searchParams: this.options.params,
            }, view => {
                this.listenTo(view, 'change', () => {
                    this.trigger('change');
                });

                this.listenTo(view, 'search', () => {
                    this.trigger('search');
                });
            });
        }
    }

    /**
     * @return {module:views/fields/base}
     */
    getFieldView() {
        return this.getView('field');
    }

    populateDefaults() {
        let view = this.getView('field');

        if (!view) {
            return;
        }

        if (!('populateSearchDefaults' in view)) {
            return;
        }

        view.populateSearchDefaults();
    }
}

export default FilterView;
PK]koN::(views/portal/fields/quick-create-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/portal/fields/quick-create-list', ['views/settings/fields/quick-create-list'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            this.params.options = this.params.options.filter(tab => {
                if (!!this.getMetadata().get('scopes.' + tab + '.aclPortal')) {
                    return true;
                }
            });
        },
    });
});
PK]>h���views/portal/fields/tab-list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/portal/fields/tab-list', ['views/settings/fields/tab-list'], function (Dep) {

    return Dep.extend({

        noGroups: true,

        setupOptions: function () {
            Dep.prototype.setupOptions.call(this);

            this.params.options = this.params.options.filter(tab => {
                if (tab === '_delimiter_') {
                    return true;
                }

                if (!!this.getMetadata().get('scopes.' + tab + '.aclPortal')) {
                    return true;
                }
            });
        },
    });
});
PK].���� views/portal/fields/custom-id.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/portal/fields/custom-id', ['views/fields/varchar'], function (Dep) {

    return Dep.extend({

        setup: function () {
            Dep.prototype.setup.call(this);

            this.listenTo(this, 'change', () => {
                var value = this.model.get('customId');

                if (!value || value === '') {
                    return;
                }

                value = value.replace(/ /i, '-').toLowerCase();
                value = encodeURIComponent(value);

                this.model.set('customId', value);
            });
        },
    });
});
PK]��Yviews/portal/record/list.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

define('views/portal/record/list', ['views/record/list'], function (Dep) {

    return Dep.extend({

        massActionList: [
            'remove',
        ],
    });
});
PK]w%hKKutils.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module utils */

const IS_MAC = /Mac/.test(navigator.userAgent);

/**
 * Utility functions.
 */
Espo.Utils = {

    /**
     * Handle a click event action.
     *
     * @param {module:view} view A view.
     * @param {MouseEvent} event An event.
     * @param {HTMLElement} element An  element.
     * @param {{
     *     action?: string,
     *     handler?: string,
     *     actionItems?: Array<{onClick?: function(), name?: string}>,
     *     className?: string,
     * }} [actionData] Data. If an action is not specified, it will be fetched from a target element.
     * @return {boolean} True if handled.
     */
    handleAction: function (view, event, element, actionData) {
        actionData = actionData || {};

        const $target = $(element);
        const action = actionData.action || $target.data('action');

        const name = $target.data('name') || action;

        if (
            name &&
            actionData.actionItems &&
            (
                !actionData.className ||
                element.classList.contains(actionData.className)
            )
        ) {
            const data = actionData.actionItems.find(item => {
                return item.name === name || item.action === name;
            });

            if (data && data.onClick) {
                data.onClick();

                return true;
            }
        }

        if (!action) {
            return false;
        }

        if (event.ctrlKey || event.metaKey || event.shiftKey) {
            const href = $target.attr('href');

            if (href && href !== 'javascript:') {
                return false;
            }
        }

        const data = $target.data();
        const method = 'action' + Espo.Utils.upperCaseFirst(action);
        const handler = actionData.handler || data.handler;

        let fired = false;

        if (handler) {
            event.preventDefault();
            event.stopPropagation();

            fired = true;

            Espo.loader.require(handler, Handler => {
                let handler = new Handler(view);

                handler[method].call(handler, data, event);
            });
        }
        else if (typeof view[method] === 'function') {
            view[method].call(view, data, event);

            event.preventDefault();
            event.stopPropagation();

            fired = true;
        }

        if (!fired) {
            return false;
        }

        this._processAfterActionDropdown($target);

        return true;
    },

    /**
     * @private
     * @param {JQuery} $target
     */
    _processAfterActionDropdown: function ($target) {
        let $dropdown = $target.closest('.dropdown-menu');

        if (!$dropdown.length) {
            return;
        }

        let $dropdownToggle = $dropdown.parent().find('[data-toggle="dropdown"]');

        if (!$dropdownToggle.length) {
            return;
        }

        let isDisabled = false;

        if ($dropdownToggle.attr('disabled')) {
            isDisabled = true;

            $dropdownToggle.removeAttr('disabled').removeClass('disabled');
        }

        // noinspection JSUnresolvedReference
        $dropdownToggle.dropdown('toggle');

        $dropdownToggle.focus();

        if (isDisabled) {
            $dropdownToggle.attr('disabled', 'disabled').addClass('disabled');
        }
    },

    /**
     * @typedef {Object} Espo.Utils~ActionAvailabilityDefs
     *
     * @property {string|null} [configCheck] A config path to check. Path items are separated
     *   by the dot. If a config value is not empty, then the action is allowed.
     *   The `!` prefix reverses the check.
     */

    /**
     * Check action availability.
     *
     * @param {module:view-helper} helper A view helper.
     * @param {Espo.Utils~ActionAvailabilityDefs} item Definitions.
     * @returns {boolean}
     */
    checkActionAvailability: function (helper, item) {
        let config = helper.config;

        if (item.configCheck) {
            let configCheck = item.configCheck;

            let opposite = false;

            if (configCheck.substring(0, 1) === '!') {
                opposite = true;

                configCheck = configCheck.substring(1);
            }

            let configCheckResult = config.getByPath(configCheck.split('.'));

            if (opposite) {
                configCheckResult = !configCheckResult;
            }

            if (!configCheckResult) {
                return false;
            }
        }

        return true;
    },

    /**
     * @typedef {Object} Espo.Utils~ActionAccessDefs
     *
     * @property {'create'|'read'|'edit'|'stream'|'delete'|null} acl An ACL action to check.
     * @property {string|null} [aclScope] A scope to check.
     * @property {string|null} [scope] Deprecated. Use `aclScope`.
     */

    /**
     * Check access to an action.
     *
     * @param {module:acl-manager} acl An ACL manager.
     * @param {string|module:model|null} [obj] A scope or a model.
     * @param {Espo.Utils~ActionAccessDefs} item Definitions.
     * @param {boolean} [isPrecise=false] To return `null` if not enough data is set in a model.
     *   E.g. the `teams` field is not yet loaded.
     * @returns {boolean|null}
     */
    checkActionAccess: function (acl, obj, item, isPrecise) {
        let hasAccess = true;

        if (item.acl) {
            if (!item.aclScope) {
                if (obj) {
                    if (typeof obj === 'string' || obj instanceof String) {
                        hasAccess = acl.check(obj, item.acl);
                    }
                    else {
                        hasAccess = acl.checkModel(obj, item.acl, isPrecise);
                    }
                }
                else {
                    hasAccess = acl.check(item.scope, item.acl);
                }
            }
            else {
                hasAccess = acl.check(item.aclScope, item.acl);
            }
        }
        else if (item.aclScope) {
            hasAccess = acl.checkScope(item.aclScope);
        }

        return hasAccess;
    },

    /**
     * @typedef {Object} Espo.Utils~AccessDefs
     *
     * @property {'create'|'read'|'edit'|'stream'|'delete'|null} action An ACL action to check.
     * @property {string|null} [scope] A scope to check.
     * @property {string[]} [portalIdList] A portal ID list. To check whether a user in one of portals.
     * @property {string[]} [teamIdList] A team ID list. To check whether a user in one of teams.
     * @property {boolean} [isPortalOnly=false] Allow for portal users only.
     * @property {boolean} [inPortalDisabled=false] Disable for portal users.
     * @property {boolean} [isAdminOnly=false] Allow for admin users only.
     */

    /**
     * Check access to an action.
     *
     * @param {Espo.Utils~AccessDefs[]} dataList List of definitions.
     * @param {module:acl-manager} acl An ACL manager.
     * @param {module:models/user} user A user.
     * @param {module:model|null} [entity] A model.
     * @param {boolean} [allowAllForAdmin=false] Allow all for an admin.
     * @returns {boolean}
     */
    checkAccessDataList: function (dataList, acl, user, entity, allowAllForAdmin) {
        if (!dataList || !dataList.length) {
            return true;
        }

        for (var i in dataList) {
            var item = dataList[i];

            if (item.scope) {
                if (item.action) {
                    if (!acl.check(item.scope, item.action)) {
                        return false;
                    }
                } else {
                    if (!acl.checkScope(item.scope)) {
                        return false;
                    }
                }
            } else if (item.action) {
                if (entity) {
                    if (!acl.check(entity, item.action)) {
                        return false;
                    }
                }
            }

            if (item.teamIdList) {
                if (user && !(allowAllForAdmin && user.isAdmin())) {
                    var inTeam = false;

                    user.getLinkMultipleIdList('teams').forEach(teamId => {
                        if (~item.teamIdList.indexOf(teamId)) {
                            inTeam = true;
                        }
                    });

                    if (!inTeam) {
                        return false;
                    }
                }
            }

            if (item.portalIdList) {
                if (user && !(allowAllForAdmin && user.isAdmin())) {
                    var inPortal = false;

                    user.getLinkMultipleIdList('portals').forEach(portalId => {
                        if (~item.portalIdList.indexOf(portalId)) {
                            inPortal = true;
                        }
                    });

                    if (!inPortal) {
                        return false;
                    }
                }
            }

            if (item.isPortalOnly) {
                if (user && !(allowAllForAdmin && user.isAdmin())) {
                    if (!user.isPortal()) {
                        return false;
                    }
                }
            }
            else if (item.inPortalDisabled) {
                if (user && !(allowAllForAdmin && user.isAdmin())) {
                    if (user.isPortal()) {
                        return false;
                    }
                }
            }

            if (item.isAdminOnly) {
                if (user) {
                    if (!user.isAdmin()) {
                        return false;
                    }
                }
            }
        }

        return true;
    },

    /**
     * @private
     * @param {string} string
     * @param {string} p
     * @returns {string}
     */
    convert: function (string, p) {
        if (string === null) {
            return string;
        }

        var result = string;

        switch (p) {
            case 'c-h':
            case 'C-h':
                result = Espo.Utils.camelCaseToHyphen(string);

                break;

            case 'h-c':
                result = Espo.Utils.hyphenToCamelCase(string);

                break;

            case 'h-C':
                result = Espo.Utils.hyphenToUpperCamelCase(string);

                break;
        }

        return result;
    },

    /**
     * Is object.
     *
     * @param {*} obj What to check.
     * @returns {boolean}
     */
    isObject: function (obj) {
        if (obj === null) {
            return false;
        }

        return typeof obj === 'object';
    },

    /**
     * A shallow clone.
     *
     * @param {*} obj An object.
     * @returns {*}
     */
    clone: function (obj) {
        if (!Espo.Utils.isObject(obj)) {
            return obj;
        }

        return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
    },

    /**
     * A deep clone.
     *
     * @param {*} data An object.
     * @returns {*}
     */
    cloneDeep: function (data) {
        data = Espo.Utils.clone(data);

        if (Espo.Utils.isObject(data) || _.isArray(data)) {
            for (var i in data) {
                data[i] = this.cloneDeep(data[i]);
            }
        }

        return data;
    },

    /**
     * Compose a class name.
     *
     * @param {string} module A module.
     * @param {string} name A name.
     * @param {string} [location=''] A location.
     * @return {string}
     */
    composeClassName: function (module, name, location) {
        if (module) {
            module = this.camelCaseToHyphen(module);
            name = this.camelCaseToHyphen(name).split('.').join('/');
            location = this.camelCaseToHyphen(location || '');

            return module + ':' + location + '/' + name;
        }
        else {
            name = this.camelCaseToHyphen(name).split('.').join('/');

            return location + '/' + name;
        }
    },

    /**
     * Compose a view class name.
     *
     * @param {string} name A name.
     * @returns {string}
     */
    composeViewClassName: function (name) {
        if (name && name[0] === name[0].toLowerCase()) {
            return name;
        }

        if (name.indexOf(':') !== -1) {
            var arr = name.split(':');
            var modPart = arr[0];
            var namePart = arr[1];

            modPart = this.camelCaseToHyphen(modPart);
            namePart = this.camelCaseToHyphen(namePart).split('.').join('/');

            return modPart + ':' + 'views' + '/' + namePart;
        }
        else {
            name = this.camelCaseToHyphen(name).split('.').join('/');

            return 'views' + '/' + name;
        }
    },

    /**
     * Convert a string from camelCase to hyphen and replace dots with hyphens.
     * Useful for setting to DOM attributes.
     *
     * @param {string} string A string.
     * @returns {string}
     */
    toDom: function (string) {
        return Espo.Utils.convert(string, 'c-h')
            .split('.')
            .join('-');
    },

    /**
     * Lower-case a first character.
     *
     * @param  {string} string A string.
     * @returns {string}
     */
    lowerCaseFirst: function (string) {
        if (string === null) {
            return string;
        }

        return string.charAt(0).toLowerCase() + string.slice(1);
    },

    /**
     * Upper-case a first character.
     *
     * @param  {string} string A string.
     * @returns {string}
     */
    upperCaseFirst: function (string) {
        if (string === null) {
            return string;
        }

        return string.charAt(0).toUpperCase() + string.slice(1);
    },

    /**
     * Hyphen to UpperCamelCase.
     *
     * @param {string} string A string.
     * @returns {string}
     */
    hyphenToUpperCamelCase: function (string) {
        if (string === null) {
            return string;
        }

        return this.upperCaseFirst(
            string.replace(
                /-([a-z])/g,
                function (g) {
                    return g[1].toUpperCase();
                }
            )
        );
    },

    /**
     * Hyphen to camelCase.
     *
     * @param {string} string A string.
     * @returns {string}
     */
    hyphenToCamelCase: function (string) {
        if (string === null) {
            return string;
        }

        return string.replace(
            /-([a-z])/g,
            function (g) {
                return g[1].toUpperCase();
            }
        );
    },

    /**
     * CamelCase to hyphen.
     *
     * @param {string} string A string.
     * @returns {string}
     */
    camelCaseToHyphen: function (string) {
        if (string === null) {
            return string;
        }

        return string.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
    },

    /**
     * Trim an ending slash.
     *
     * @param {String} str A string.
     * @returns {string}
     */
    trimSlash: function (str) {
        if (str.slice(-1) === '/') {
            return str.slice(0, -1);
        }

        return str;
    },

    /**
     * Parse params in string URL options.
     *
     * @param {string} string An URL part.
     * @returns {Object.<string,string>}
     */
    parseUrlOptionsParam: function (string) {
        if (!string) {
            return {};
        }

        if (string.indexOf('&') === -1 && string.indexOf('=') === -1) {
            return {};
        }

        let options = {};

        if (typeof string !== 'undefined') {
            string.split('&').forEach(item => {
                let p = item.split('=');

                options[p[0]] = true;

                if (p.length > 1) {
                    options[p[0]] = p[1];
                }
            });
        }

        return options;
    },

    /**
     * Key a key from a key-event.
     *
     * @param {JQueryKeyEventObject|KeyboardEvent} e A key event.
     * @return {string}
     */
    getKeyFromKeyEvent: function (e) {
        let key = e.code;

        key = keyMap[key] || key;

        if (e.shiftKey) {
            key = 'Shift+' + key;
        }

        if (e.altKey) {
            key = 'Alt+' + key;
        }

        if (IS_MAC ? e.metaKey : e.ctrlKey) {
            key = 'Control+' + key;
        }

        return key;
    },

    /**
     * Generate an ID. Not to be used by 3rd party code.
     *
     * @internal
     * @return {string}
     */
    generateId: function () {
        return (Math.floor(Math.random() * 10000001)).toString()
    },

    /**
     * Not to be used in custom code. Can be removed in future versions.
     * @internal
     * @return {string}
     */
    obtainBaseUrl: function () {
        let baseUrl = window.location.origin + window.location.pathname;

        if (baseUrl.slice(-1) !== '/') {
            baseUrl = window.location.pathname.includes('.') ?
                baseUrl.slice(0, baseUrl.lastIndexOf('/')) + '/' :
                baseUrl + '/';
        }

        return baseUrl;
    }
};

const keyMap = {
    'NumpadEnter': 'Enter',
};

/**
 * @deprecated Use `Espo.Utils`.
 */
Espo.utils = Espo.Utils;

export default Espo.Utils;
PK]BKax~e~e
collection.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module collection */

import Model from 'model';
import {Events, View as BullView} from 'bullbone';
import _ from 'underscore';

/**
 * On sync with backend.
 *
 * @event Collection#sync
 * @param {Collection} collection A collection.
 * @param {Object} response Response from backend.
 * @param {Object} o Options.
 */

/**
 * Any number of models have been added, removed or changed.
 *
 * @event Collection#update
 * @param {Collection} collection A collection.
 * @param {Object} o Options.
 */

/**
 * On reset.
 *
 * @event Collection#reset
 * @param {Collection} collection A collection.
 * @param {Object} o Options.
 */

/**
 * A collection.
 *
 * @mixes Bull.Events
 * @copyright Credits to Backbone.js.
 */
class Collection {

    /**
     * An entity type.
     *
     * @type {string|null}
     */
    entityType = null

    /**
     * A total number of records.
     *
     * @type {number}
     */
    total = 0

    /**
     * A current offset (for pagination).
     *
     * @type {number}
     */
    offset = 0

    /**
     * A max size (for pagination).
     *
     * @type {number}
     */
    maxSize = 20

    /**
     * An order.
     *
     * @type {boolean|'asc'|'desc'|null}
     */
    order = null

    /**
     * An order-by field.
     *
     * @type {string|null}
     */
    orderBy = null

    /**
     * A where clause.
     *
     * @type {Array.<Object>|null}
     */
    where = null

    /**
     * @deprecated
     */
    whereAdditional = null

    /**
     * A length correction.
     *
     * @type {number}
     */
    lengthCorrection = 0

    /**
     * A max max-size.
     *
     * @type {number}
     */
    maxMaxSize = 0

    /**
     * A where function.
     *
     * @type {function(): Object[]}
     */
    whereFunction

    /**
     * A last sync request promise.
     *
     * @type {module:ajax.AjaxPromise|null}
     */
    lastSyncPromise = null

    /**
     * @param {Model[]|null} [models] Models.
     * @param {{
     *     entityType?: string,
     *     model?: Model.prototype,
     *     defs?: module:model~defs,
     *     order?: 'asc'|'desc'|boolean|null,
     *     orderBy?: string|null,
     *     urlRoot?: string,
     *     url?: string,
     * }} [options] Options.
     */
    constructor(models, options) {
        options = {...options};

        if (options.model) {
            this.model = options.model;
        }

        this._reset();

        if (options.entityType) {
            this.entityType = options.entityType;
            /** @deprecated */
            this.name = this.entityType;
        }

        /**
         * A root URL.
         *
         * @public
         * @type {string|null}
         */
        this.urlRoot = options.urlRoot || this.urlRoot || this.entityType;

        /**
         * An URL.
         *
         * @type {string|null}
         */
        this.url = options.url || this.url || this.urlRoot;

        this.orderBy = options.orderBy || this.orderBy;
        this.order = options.order || this.order;

        this.defaultOrder = this.order;
        this.defaultOrderBy = this.orderBy;

        /** @type {module:model~defs} */
        this.defs = options.defs || {};

        this.data = {};

        /**
         * @private
         * @type {Model#}
         */
        this.model = options.model || Model;

        if (models) {
            this.reset(models, {silent: true, ...options});
        }
    }

    /**
     * Add models or a model.
     *
     * @param {Model[]|Model} models Models ar a model.
     * @param {{
     *     merge?: boolean,
     *     at?: number,
     *     silent?: boolean,
     * }} [options] Options. `at` – position; `merge` – merge existing models, otherwise, they are ignored.
     * @return {this}
     * @fires Collection#update
     */
    add(models, options) {
        this.set(models, {merge: false, ...options, ...addOptions});

        return this;
    }

    /**
     * Remove models or a model.
     *
     * @param {Model[]|Model|string} models Models, a model or a model ID.
     * @param {{
     *     silent?: boolean,
     * } & Object.<string, *>} [options] Options.
     * @return {this}
     * @fires Collection#update
     */
    remove(models, options) {
        options = {...options};

        let singular = !_.isArray(models);

        models = singular ? [models] : models.slice();

        let removed = this._removeModels(models, options);

        if (!options.silent && removed.length) {
            options.changes = {
                added: [],
                merged: [],
                removed: removed,
            };

            this.trigger('update', this, options);
        }

        return this;
    }

    /**
     * @protected
     * @param {Model[]|Model} models Models ar a model.
     * @param {{
     *     silent?: boolean,
     *     at?: number,
     *     prepare?: boolean,
     *     add?: boolean,
     *     merge?: boolean,
     *     remove?: boolean,
     *     index?: number,
     * } & Object.<string, *>} [options]
     * @return {Model[]}
     */
    set(models, options) {
        if (models == null) {
            return [];
        }

        options = {...setOptions, ...options};

        if (options.prepare && !this._isModel(models)) {
            models = this.prepareAttributes(models, options) || [];
        }

        let singular = !_.isArray(models);
        models = singular ? [models] : models.slice();

        let at = options.at;

        if (at != null) {
            at = +at;
        }

        if (at > this.length) {
            at = this.length;
        }

        if (at < 0) {
            at += this.length + 1;
        }

        let set = [];
        let toAdd = [];
        let toMerge = [];
        let toRemove = [];
        let modelMap = {};

        let add = options.add;
        let merge = options.merge;
        let remove = options.remove;

        let model, i;

        for (i = 0; i < models.length; i++) {
            model = models[i];

            let existing = this._get(model);

            if (existing) {
                if (merge && model !== existing) {
                    let attributes = this._isModel(model) ?
                        model.attributes :
                        model;

                    if (options.prepare) {
                        attributes = existing.prepareAttributes(attributes, options);
                    }

                    existing.set(attributes, options);
                    toMerge.push(existing);
                }

                if (!modelMap[existing.cid]) {
                    modelMap[existing.cid] = true;
                    set.push(existing);
                }

                models[i] = existing;
            }
            else if (add) {
                model = models[i] = this._prepareModel(model);

                if (model) {
                    toAdd.push(model);

                    this._addReference(model, options);

                    modelMap[model.cid] = true;
                    set.push(model);
                }
            }
        }

        // Remove stale models.
        if (remove) {
            for (i = 0; i < this.length; i++) {
                model = this.models[i];

                if (!modelMap[model.cid]) {
                    toRemove.push(model);
                }
            }

            if (toRemove.length) {
                this._removeModels(toRemove, options);
            }
        }

        let orderChanged = false;
        let replace = add && remove;

        if (set.length && replace) {
            orderChanged =
                this.length !== set.length ||
                _.some(this.models, (m, index) => {
                    return m !== set[index];
                });

            this.models.length = 0;
            splice(this.models, set, 0);

            this.length = this.models.length;
        }
        else if (toAdd.length) {
            splice(this.models, toAdd, at == null ? this.length : at);

            this.length = this.models.length;
        }

        if (!options.silent) {
            for (i = 0; i < toAdd.length; i++) {
                if (at != null) {
                    options.index = at + i;
                }

                model = toAdd[i];

                model.trigger('add', model, this, options);
            }

            if (orderChanged) {
                this.trigger('sort', this, options);
            }

            if (toAdd.length || toRemove.length || toMerge.length) {
                options.changes = {
                    added: toAdd,
                    removed: toRemove,
                    merged: toMerge
                };

                this.trigger('update', this, options);
            }
        }

        return models;
    }

    /**
     * Reset.
     *
     * @param {Model[]|null} [models] Models to replace the collection with.
     * @param {{
     *     silent?: boolean,
     * } & Object.<string, *>} [options]
     * @return {this}
     * @fires Collection#reset
     */
    reset(models, options) {
        this.lengthCorrection = 0;

        options = options ? _.clone(options) : {};

        for (let i = 0; i < this.models.length; i++) {
            this._removeReference(this.models[i], options);
        }

        options.previousModels = this.models;

        this._reset();

        if (models) {
            this.add(models, {silent: true, ...options});
        }

        if (!options.silent) {
            this.trigger('reset', this, options);
        }

        return this;
    }

    /**
     * Add a model at the end.
     *
     * @param {Model} model A model.
     * @param {{
     *     silent?: boolean,
     * }} [options] Options
     * @return {this}
     */
    push(model, options) {
        this.add(model, {at: this.length, ...options});

        return this;
    }

    /**
     * Remove and return the last model.
     *
     * @param {{
     *     silent?: boolean,
     * }} [options] Options
     * @return {Model|null}
     */
    pop(options) {
        let model = this.at(this.length - 1);

        if (!model) {
            return null;
        }

        this.remove(model, options);

        return model;
    }

    /**
     * Add a model to the beginning.
     *
     * @param {Model} model A model.
     * @param {{
     *     silent?: boolean,
     * }} [options] Options
     * @return {this}
     */
    unshift(model, options) {
        this.add(model, {at: 0, ...options});

        return this;
    }

    /**
     * Remove and return the first model.
     *
     * @param {{
     *     silent?: boolean,
     * }} [options] Options
     * @return {Model|null}
     */
    shift(options) {
        let model = this.at(0);

        if (!model) {
            return null;
        }

        this.remove(model, options);

        return model;
    }

    /**
     * Get a model by an ID.
     *
     * @todo Usage to _get.
     * @param {string} id An ID.
     * @return {Model|undefined}
     */
    get(id) {
        return this._get(id);
    }

    /**
     * Whether a model in the collection.
     *
     * @todo Usage to _has.
     * @param {string} id An ID.
     * @return {boolean}
     */
    has(id) {
        return this._has(id);
    }

    /**
     * Get a model by index.
     *
     * @param {number} index An index. Can be negative, then counted from the end.
     * @return {Model|undefined}
     */
    at(index) {
        if (index < 0) {
            index += this.length;
        }

        return this.models[index];
    }

    /**
     * Iterates through a collection.
     *
     * @param {function(Model)} callback A function.
     * @param {Object} [context] A context.
     */
    forEach(callback, context) {
        return this.models.forEach(callback, context);
    }

    /**
     * Get an index of a model. Returns -1 if not found.
     *
     * @param {Model} model A model
     * @return {number}
     */
    indexOf(model) {
        return this.models.indexOf(model);
    }

    /**
     * @private
     * @param {string|Object.<string, *>|Model} obj
     * @return {boolean}
     */
    _has(obj) {
        return !!this._get(obj)
    }

    /**
     * @private
     * @param {string|Object.<string, *>|Model} obj
     * @return {Model|undefined}
     */
    _get(obj) {
        if (obj == null) {
            return void 0;
        }

        return this._byId[obj] ||
            this._byId[this.modelId(obj.attributes || obj)] ||
            obj.cid && this._byId[obj.cid];
    }

    /**
     * @protected
     * @param {Object.<string, *>} attributes
     * @return {*}
     */
    modelId(attributes) {
        return attributes['id'];
    }

    /** @private */
    _reset() {
        /**
         * A number of records.
         */
        this.length = 0;

        /**
         * Models.
         *
         * @type {Model[]}
         */
        this.models = [];

        /** @private */
        this._byId  = {};
    }

    /**
     * @param {string} orderBy An order field.
     * @param {bool|null|'desc'|'asc'} [order] True for desc.
     * @returns {Promise}
     */
    sort(orderBy, order) {
        this.orderBy = orderBy;

        if (order === true) {
            order = 'desc';
        }
        else if (order === false) {
            order = 'asc';
        }

        this.order = order || 'asc';

        return this.fetch();
    }

    /**
     * Next page.
     */
    nextPage() {
        this.setOffset(this.offset + this.maxSize);
    }

    /**
     * Previous page.
     */
    previousPage() {
        this.setOffset(this.offset - this.maxSize);
    }

    /**
     * First page.
     */
    firstPage() {
        this.setOffset(0);
    }

    /**
     * Last page.
     */
    lastPage() {
        let offset = this.total - this.total % this.maxSize;

        if (offset === this.total) {
            offset = this.total - this.maxSize;
        }

        this.setOffset(offset);
    }

    /**
     * Set an offset.
     *
     * @param {number} offset Offset.
     */
    setOffset(offset) {
        if (offset < 0) {
            throw new RangeError('offset can not be less than 0');
        }

        if (offset > this.total && this.total !== -1 && offset > 0) {
            throw new RangeError('offset can not be larger than total count');
        }

        this.offset = offset;
        this.fetch();
    }

    /**
     * Has more.
     *
     * @return {boolean}
     */
    hasMore() {
        return this.total > this.length || this.total === -1;
    }

    /**
     * Prepare attributes.
     *
     * @protected
     * @param {*} response A response from the backend.
     * @param {Object.<string, *>} options Options.
     * @returns {Object.<string, *>[]}
     */
    prepareAttributes(response, options) {
        this.total = response.total;
        this.dataAdditional = response.additionalData || null;

        return response.list;
    }

    /**
     * @deprecated As of v8.0. Use `prepareAttributes`.
     * @todo Remove in v9.0.
     */
    parse(response, options) {
        return this.prepareAttributes(response, options);
    }

    /**
     * Fetch from the backend.
     *
     * @param {{
     *     remove?: boolean,
     *     more?: boolean,
     * } & Object.<string, *>} [options] Options.
     * @returns {Promise}
     * @fires Collection#sync Unless `{silent: true}`.
     */
    fetch(options) {
        options = {...options};

        options.data = {...options.data, ...this.data};

        this.offset = options.offset || this.offset;
        this.orderBy = options.orderBy || this.orderBy;
        this.order = options.order || this.order;
        this.where = options.where || this.where;

        let length = this.length + this.lengthCorrection;

        if (!('maxSize' in options)) {
            options.data.maxSize = options.more ? this.maxSize : (
                (length > this.maxSize) ? length : this.maxSize
            );

            if (this.maxMaxSize && options.data.maxSize > this.maxMaxSize) {
                options.data.maxSize = this.maxMaxSize;
            }
        }
        else {
            options.data.maxSize = options.maxSize;
        }

        options.data.offset = options.more ? length : this.offset;
        options.data.orderBy = this.orderBy;
        options.data.order = this.order;
        options.data.where = this.getWhere();

        options = {prepare: true, ...options};

        let success = options.success;

        options.success = response => {
            options.reset ?
                this.reset(response, options) :
                this.set(response, options);

            if (success) {
                success.call(options.context, this, response, options);
            }

            this.trigger('sync', this, response, options);
        };

        let error = options.error;

        options.error = response => {
            if (error) {
                error.call(options.context, this, response, options);
            }

            this.trigger('error', this, response, options);
        };

        this.lastSyncPromise = Model.prototype.sync.call(this, 'read', this, options);

        return this.lastSyncPromise;
    }

    /**
     * Abort the last fetch.
     */
    abortLastFetch() {
        if (this.lastSyncPromise && this.lastSyncPromise.getReadyState() < 4) {
            this.lastSyncPromise.abort();
        }
    }

    /**
     * Get a where clause.
     *
     * @returns {Object[]}
     */
    getWhere() {
        let where = (this.where || []).concat(this.whereAdditional || []);

        if (this.whereFunction) {
            where = where.concat(this.whereFunction() || []);
        }

        return where;
    }

    /**
     * Get an entity type.
     *
     * @returns {string}
     */
    getEntityType() {
        return this.entityType || this.name;
    }

    /**
     * Reset the order to default.
     */
    resetOrderToDefault() {
        this.orderBy = this.defaultOrderBy;
        this.order = this.defaultOrder;
    }

    /**
     * Set an order.
     *
     * @param {string|null} orderBy
     * @param {boolean|'asc'|'desc'|null} [order]
     * @param {boolean} [setDefault]
     */
    setOrder(orderBy, order, setDefault) {
        this.orderBy = orderBy;
        this.order = order;

        if (setDefault) {
            this.defaultOrderBy = orderBy;
            this.defaultOrder = order;
        }
    }

    /**
     * Clone.
     *
     * @return {Collection}
     */
    clone() {
        const collection = new this.constructor(this.models, {
            model: this.model,
            entityType: this.entityType,
            defs: this.defs,
            orderBy: this.orderBy,
            order: this.order,
        });

        collection.name = this.name;
        collection.urlRoot = this.urlRoot;
        collection.url = this.url;
        collection.defaultOrder = this.defaultOrder;
        collection.defaultOrderBy = this.defaultOrderBy;
        collection.data = Espo.Utils.cloneDeep(this.data);
        collection.where = Espo.Utils.cloneDeep(this.where);
        collection.whereAdditional = Espo.Utils.cloneDeep(this.whereAdditional);
        collection.total = this.total;
        collection.offset = this.offset;
        collection.maxSize = this.maxSize;
        collection.maxMaxSize = this.maxMaxSize;
        collection.whereFunction = this.whereFunction;

        return collection;
    }

    /**
     * Prepare an empty model instance.
     *
     * @return {Model}
     */
    prepareModel() {
        return this._prepareModel({});
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * Compose a URL for syncing. Called from Model.sync.
     *
     * @protected
     * @return {string}
     */
    composeSyncUrl() {
        return this.url;
    }

    /** @private */
    _isModel(object) {
        return object instanceof Model;
    }

    /** @private */
    _removeModels(models, options) {
        let removed = [];

        for (let i = 0; i < models.length; i++) {
            let model = this.get(models[i]);

            if (!model) {
                continue;
            }

            let index = this.models.indexOf(model);

            this.models.splice(index, 1);
            this.length--;

            delete this._byId[model.cid];
            let id = this.modelId(model.attributes);

            if (id != null) {
                delete this._byId[id];
            }

            if (!options.silent) {
                options.index = index;

                model.trigger('remove', model, this, options);
            }

            removed.push(model);

            this._removeReference(model, options);
        }

        return removed;
    }

    /** @private */
    _addReference(model) {
        this._byId[model.cid] = model;

        let id = this.modelId(model.attributes);

        if (id != null) {
            this._byId[id] = model;
        }

        model.on('all', this._onModelEvent, this);
    }

    /** @private */
    _removeReference(model) {
        delete this._byId[model.cid];

        let id = this.modelId(model.attributes);

        if (id != null) {
            delete this._byId[id];
        }

        if (this === model.collection) {
            delete model.collection;
        }

        model.off('all', this._onModelEvent, this);
    }

    /** @private */
    _onModelEvent(event, model, collection, options) {
        if (event === 'sync' && collection !== this) {
            return;
        }

        if (!model) {
            this.trigger.apply(this, arguments);

            return;
        }

        if ((event === 'add' || event === 'remove') && collection !== this) {
            return;
        }

        if (event === 'destroy') {
            this.remove(model, options);
        }

        if (event === 'change') {
            let prevId = this.modelId(model.previousAttributes());
            let id = this.modelId(model.attributes);

            if (prevId !== id) {
                if (prevId != null) {
                    delete this._byId[prevId];
                }

                if (id != null) {
                    this._byId[id] = model;
                }
            }
        }

        this.trigger.apply(this, arguments);
    }

    // noinspection JSDeprecatedSymbols
    /** @private*/
    _prepareModel(attributes) {
        if (this._isModel(attributes)) {
            if (!attributes.collection) {
                attributes.collection = this;
            }

            return attributes;
        }

        const ModelClass = this.model;

        // noinspection JSValidateTypes
        return new ModelClass(attributes, {
            collection: this,
            entityType: this.entityType || this.name,
            defs: this.defs,
        });
    }
}

Object.assign(Collection.prototype, Events);

Collection.extend = BullView.extend;

const setOptions = {
    add: true,
    remove: true,
    merge: true,
};

const addOptions = {
    add: true,
    remove: false,
};

const splice = (array, insert, at) => {
    at = Math.min(Math.max(at, 0), array.length);

    let tail = Array(array.length - at);
    let length = insert.length;
    let i;

    for (i = 0; i < tail.length; i++) {
        tail[i] = array[i + at];
    }

    for (i = 0; i < length; i++) {
        array[i + at] = insert[i];
    }

    for (i = 0; i < tail.length; i++) {
        array[i + length + at] = tail[i];
    }
};

export default Collection;
PK]A�8(�@�@email-helper.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module email-helper */

/**
 * An email helper.
 */
class EmailHelper {

    /**
     * @param {module:language} language A language.
     * @param {module:models/user} user A user.
     * @param {module:date-time} dateTime A date-time util.
     * @param {module:acl-manager} acl An ACL manager.
     */
    constructor(language, user, dateTime, acl) {
        /** @private */
        this.language = language;
        /** @private */
        this.user = user;
        /** @private */
        this.dateTime = dateTime;
        /** @private */
        this.acl = acl;

        /** @private */
        this.erasedPlaceholder = 'ERASED:';
    }

    /**
     * @returns {module:language}
     */
    getLanguage() {
        return this.language;
    }

    /**
     * @returns {module:models/user}
     */
    getUser() {
        return this.user;
    }

    /**
     * @returns {module:date-time}
     */
    getDateTime() {
        return this.dateTime;
    }

    /**
     * Get reply email attributes.
     *
     * @param {module:model} model An email model.
     * @param {Object|null} [data=null] Action data. Unused.
     * @param {boolean} [cc=false] To include CC (reply-all).
     * @returns {Object.<string, *>}
     */
    getReplyAttributes(model, data, cc) {
        let attributes = {
            status: 'Draft',
            isHtml: model.get('isHtml'),
        };

        let subject = model.get('name') || '';

        attributes['name'] = subject.toUpperCase().indexOf('RE:') !== 0 ?
            'Re: ' + subject :
            subject;

        let to = '';
        let isReplyOnSent = false;
        let nameHash = model.get('nameHash') || {};
        let replyToAddressString = model.get('replyTo') || null;
        let replyToString = model.get('replyToString') || null;
        let userEmailAddressList = this.getUser().get('emailAddressList') || [];

        if (replyToAddressString) {
            let replyToAddressList = replyToAddressString.split(';');

            to = replyToAddressList.join(';');
        }
        else if (replyToString) {
            let a = [];

            replyToString.split(';').forEach(item => {
                let part = item.trim();
                let address = this.parseAddressFromStringAddress(item);

                if (address) {
                    a.push(address);

                    let name = this.parseNameFromStringAddress(part);

                    if (name && name !== address) {
                        nameHash[address] = name;
                    }
                }
            });

            to = a.join(';');
        }

        if (
            (!to || !to.includes('@')) &&
            model.get('from')
        ) {
            if (!userEmailAddressList.includes(model.get('from'))) {
                to = model.get('from');

                if (!nameHash[to]) {
                    let fromString = model.get('fromString') || model.get('fromName');

                    if (fromString) {
                        let name = this.parseNameFromStringAddress(fromString);

                        if (name !== to) {
                            nameHash[to] = name;
                        }
                    }
                }
            }
            else {
                isReplyOnSent = true;
            }
        }

        attributes.to = to;

        if (cc) {
            attributes.cc = model.get('cc') || '';

            (model.get('to') || '').split(';').forEach(item => {
                item = item.trim();

                if (item !== this.getUser().get('emailAddress')) {
                    if (isReplyOnSent) {
                        if (attributes.to) {
                            attributes.to += ';';
                        }

                        attributes.to += item;
                    }
                    else {
                        if (attributes.cc) {
                            attributes.cc += ';';
                        }

                        attributes.cc += item;
                    }
                }
            });

            attributes.cc = attributes.cc.replace(/^(; )/,"");
        }

        if (attributes.to) {
            let toList = attributes.to.split(';');

            toList = toList.filter(item => {
                if (item.indexOf(this.erasedPlaceholder) === 0) {
                    return false;
                }

                return true;
            });

            attributes.to = toList.join(';');
        }

        if (attributes.cc) {
            let ccList = attributes.cc.split(';');

            ccList = ccList.filter(item => {
                if (item.indexOf(this.erasedPlaceholder) === 0) {
                    return false;
                }

                return true;
            });

            attributes.cc = ccList.join(';');
        }

        if (model.get('parentId')) {
            attributes['parentId'] = model.get('parentId');
            attributes['parentName'] = model.get('parentName');
            attributes['parentType'] = model.get('parentType');
        }

        if (model.get('teamsIds') && model.get('teamsIds').length) {
            attributes.teamsIds = Espo.Utils.clone(model.get('teamsIds'));
            attributes.teamsNames = Espo.Utils.clone(model.get('teamsNames') || {});

            let defaultTeamId = this.user.get('defaultTeamId');

            if (defaultTeamId && !~attributes.teamsIds.indexOf(defaultTeamId)) {
                attributes.teamsIds.push(this.user.get('defaultTeamId'));
                attributes.teamsNames[this.user.get('defaultTeamId')] = this.user.get('defaultTeamName');
            }

            attributes.teamsIds = attributes.teamsIds.filter(teamId => {
                return this.acl.checkTeamAssignmentPermission(teamId);
            });
        }

        attributes.nameHash = nameHash;
        attributes.repliedId = model.id;
        attributes.inReplyTo = model.get('messageId');


        let toAddressList = (model.get('to') || '').split(';');
        let userPersonalEmailAddressList = this.getUser().get('userEmailAddressList') || [];

        for (let address of userPersonalEmailAddressList) {
            if (toAddressList.includes(address)) {
                attributes.from = address;

                break;
            }
        }

        this.addReplyBodyAttributes(model, attributes);

        return attributes;
    }

    /**
     * Get forward email attributes.
     *
     * @param {module:model} model An email model.
     * @returns {Object}
     */
    getForwardAttributes(model) {
        let attributes = {
            status: 'Draft',
            isHtml: model.get('isHtml'),
        };

        let subject = model.get('name');

        if (~!subject.toUpperCase().indexOf('FWD:') && ~!subject.toUpperCase().indexOf('FW:')) {
            attributes['name'] = 'Fwd: ' + subject;
        }
        else {
            attributes['name'] = subject;
        }

        if (model.get('parentId')) {
            attributes['parentId'] = model.get('parentId');
            attributes['parentName'] = model.get('parentName');
            attributes['parentType'] = model.get('parentType');
        }

        this.addForwardBodyAttributes(model, attributes);

        return attributes;
    }

    /**
     * Add body attributes for a forward email.
     *
     * @param {module:model} model An email model.
     * @param {Object} attributes
     */
    addForwardBodyAttributes(model, attributes) {
        let prepending = '';

        if (model.get('isHtml')) {
            prepending = '<br>' + '------' +
                this.getLanguage().translate('Forwarded message', 'labels', 'Email') + '------';
        }
        else {
            prepending = '\n\n' + '------' +
                this.getLanguage().translate('Forwarded message', 'labels', 'Email') + '------';
        }

        let list = [];

        if (model.get('from')) {
            let from = model.get('from');
            let line = this.getLanguage().translate('from', 'fields', 'Email') + ': ';

            let nameHash = model.get('nameHash') || {};

            if (from in nameHash) {
                line += nameHash[from] + ' ';
            }

            if (model.get('isHtml')) {
                line += '&lt;' + from + '&gt;';
            }
            else {
                line += '<' + from + '>';
            }

            list.push(line);
        }

        if (model.get('dateSent')) {
            let line = this.getLanguage().translate('dateSent', 'fields', 'Email') + ': ';
            line += this.getDateTime().toDisplay(model.get('dateSent'));

            list.push(line);
        }

        if (model.get('name')) {
            let line = this.getLanguage().translate('subject', 'fields', 'Email') + ': ';

            line += model.get('name');

            list.push(line);
        }

        if (model.get('to')) {
            let line = this.getLanguage().translate('to', 'fields', 'Email') + ': ';

            let partList = [];

            model.get('to').split(';').forEach(to => {
                let nameHash = model.get('nameHash') || {};
                let line = '';

                if (to in nameHash) {
                    line += nameHash[to] + ' ';
                }

                if (model.get('isHtml')) {
                    line += '&lt;' + to + '&gt;';
                }
                else {
                    line += '<' + to + '>';
                }

                partList.push(line);
            });

            line += partList.join(';');

            list.push(line);
        }

        list.forEach(line => {
            if (model.get('isHtml')) {
                prepending += '<br>' + line;
            }
            else {
                prepending += '\n' + line;
            }
        });

        if (model.get('isHtml')) {
            let body = model.get('body');

            attributes['body'] = prepending + '<br><br>' + body;
        }
        else {
            let bodyPlain = model.get('body') || model.get('bodyPlain') || '';

            attributes['bodyPlain'] = attributes['body'] = prepending + '\n\n' + bodyPlain;
        }
    }

    /**
     * Parse a name from a string address.
     *
     * @param {string} value A string address. E.g. `Test Name <address@domain>`.
     * @returns {string|null}
     */
    parseNameFromStringAddress(value) {
        if (~value.indexOf('<')) {
            let name = value.replace(/<(.*)>/, '').trim();

            if (name.charAt(0) === '"' && name.charAt(name.length - 1) === '"') {
                name = name.slice(1, name.length - 2);
            }

            return name;
        }

        return null;
    }

    /**
     * Parse an address from a string address.
     *
     * @param {string} value A string address. E.g. `Test Name <address@domain>`.
     * @returns {string|null}
     */
    parseAddressFromStringAddress(value) {
        let r = value.match(/<(.*)>/);
        let address;

        if (r && r.length > 1) {
            address = r[1];
        }
        else {
            address = value.trim();
        }

        return address;
    }

    /**
     * Add body attributes for a reply email.
     *
     * @param {module:model} model An email model.
     * @param {Object.<string, *>} attributes
     */
    addReplyBodyAttributes(model, attributes) {
        let format = this.getDateTime().getReadableShortDateTimeFormat();

        let dateSent = model.get('dateSent');

        let dateSentSting = null;

        if (dateSent) {
            let dateSentMoment = this.getDateTime().toMoment(dateSent);

            dateSentSting = dateSentMoment.format(format);
        }

        let replyHeadString =
            (dateSentSting || this.getLanguage().translate('Original message', 'labels', 'Email'));

        let fromName = model.get('fromName');

        if (!fromName && model.get('from')) {
            fromName = (model.get('nameHash') || {})[model.get('from')];

            if (fromName) {
                replyHeadString += ', ' + fromName;
            }
        }

        replyHeadString += ':';

        if (model.get('isHtml')) {
            let body = model.get('body');

            body = '<p>&nbsp;</p><p>' +  replyHeadString + '</p><blockquote>' +  body + '</blockquote>';

            attributes['body'] = body;
        }
        else {
            let bodyPlain = model.get('body') || model.get('bodyPlain') || '';

            let b = '\n\n';

            b += replyHeadString + '\n';

            bodyPlain.split('\n').forEach(line => {
                b += '> ' + line + '\n';
            });

            bodyPlain = b;

            attributes['body'] = bodyPlain;
            attributes['bodyPlain'] = bodyPlain;
        }
    }

    /**
     * Compose a mailto link.
     *
     * @param {Object} attributes Attributes.
     * @param {string} [bcc] BCC.
     * @returns {string} A mailto link.
     */
    composeMailToLink(attributes, bcc) {
        let link = 'mailto:';

        link += (attributes.to || '').split(';').join(',');

        let o = {};

        if (attributes.cc) {
            o.cc = attributes.cc.split(';').join(',');
        }

        if (attributes.bcc) {
            if (!bcc) {
                bcc = '';
            } else {
                bcc += ';';
            }

            bcc += attributes.bcc;
        }

        if (bcc) {
            o.bcc = bcc.split(';').join(',');
        }

        if (attributes.name) {
            o.subject = attributes.name;
        }

        if (attributes.body) {
            o.body = attributes.body;

            if (attributes.isHtml) {
                o.body = this.htmlToPlain(o.body);
            }
        }

        if (attributes.inReplyTo) {
            o['In-Reply-To'] = attributes.inReplyTo;
        }

        let part = '';

        for (let key in o) {
            if (part !== '') {
                part += '&';
            }
            else {
                part += '?';
            }

            part += key + '=' + encodeURIComponent(o[key]);
        }

        link += part;

        return link;
    }

    /**
     * Convert an HTML to a plain text.
     *
     * @param {string} text A text.
     * @returns {string}
     */
    htmlToPlain(text) {
        text = text || '';

        let value = text.replace(/<br\s*\/?>/mg, '\n');

        value = value.replace(/<\/p\s*\/?>/mg, '\n\n');

        let $div = $('<div>').html(value);

        $div.find('style').remove();
        $div.find('link[ref="stylesheet"]').remove();

        value =  $div.text();

        return value;
    }
}

export default EmailHelper;
PK]qH����theme-manager.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module theme-manager */

/**
 * A theme manager.
 */
class ThemeManager {

    /**
     * @param {module:models/settings} config A config.
     * @param {module:models/preferences} preferences Preferences.
     * @param {module:metadata} metadata Metadata.
     * @param {?string} [name] A name. If not set, then will be obtained from config and preferences.
     */
    constructor(config, preferences, metadata, name) {
        /**
         * @private
         * @type {module:models/settings}
         */
        this.config = config;

        /**
         * @private
         * @type {module:models/preferences}
         */
        this.preferences = preferences;

        /**
         * @private
         * @type {module:metadata}
         */
        this.metadata = metadata;

        /**
         * @private
         * @type {?string}
         */
        this.name = name || null;
    }

    /**
     * @private
     */
    defaultParams = {
        screenWidthXs: 768,
        dashboardCellHeight: 155,
        dashboardCellMargin: 19,
    }

    /**
     * Get a theme name for the current user.
     *
     * @returns {string}
     */
    getName() {
        if (this.name) {
            return this.name;
        }

        if (!this.config.get('userThemesDisabled')) {
            let name = this.preferences.get('theme');

            if (name && name !== '') {
                return name;
            }
        }

        return this.config.get('theme');
    }

    /**
     * Get a theme name currently applied to the DOM.
     *
     * @returns {string|null} Null if not applied.
     */
    getAppliedName() {
        let name = window.getComputedStyle(document.body).getPropertyValue('--theme-name');

        if (!name) {
            return null;
        }

        return name.trim();
    }

    /**
     * Whether a current theme is applied to the DOM.
     *
     * @returns {boolean}
     */
    isApplied() {
        let appliedName = this.getAppliedName();

        if (!appliedName) {
            return true;
        }

        return this.getName() === appliedName;
    }

    /**
     * Get a stylesheet path for a current theme.
     *
     * @returns {string}
     */
    getStylesheet() {
        let link = this.getParam('stylesheet') || 'client/css/espo/espo.css';

        if (this.config.get('cacheTimestamp')) {
            link += '?r=' + this.config.get('cacheTimestamp').toString();
        }

        return link;
    }

    /**
     * Get an iframe stylesheet path for a current theme.
     *
     * @returns {string}
     */
    getIframeStylesheet() {
        let link = this.getParam('stylesheetIframe') || 'client/css/espo/espo-iframe.css';

        if (this.config.get('cacheTimestamp')) {
            link += '?r=' + this.config.get('cacheTimestamp').toString();
        }

        return link;
    }

    /**
     * Get an iframe-fallback stylesheet path for a current theme.
     *
     * @returns {string}
     */
    getIframeFallbackStylesheet() {
        let link = this.getParam('stylesheetIframeFallback') || 'client/css/espo/espo-iframe.css'

        if (this.config.get('cacheTimestamp')) {
            link += '?r=' + this.config.get('cacheTimestamp').toString();
        }

        return link;
    }

    /**
     * Get a theme parameter.
     *
     * @param {string} name A parameter name.
     * @returns {*} Null if not set.
     */
    getParam(name) {
        if (name !== 'params' && name !== 'mappedParams') {
            let varValue = this.getVarParam(name);

            if (varValue !== null) {
                return varValue;
            }

            let mappedValue = this.getMappedParam(name);

            if (mappedValue !== null) {
                return mappedValue;
            }
        }

        let value = this.metadata.get(['themes', this.getName(), name]);

        if (value !== null) {
            return value;
        }

        value = this.metadata.get(['themes', this.getParentName(), name]);

        if (value !== null) {
            return value;
        }

        return this.defaultParams[name] || null;
    }

    /**
     * @private
     * @param {string} name
     * @returns {*}
     */
    getVarParam(name) {
        let params = this.getParam('params') || {};

        if (!(name in params)) {
            return null;
        }

        let values = null;

        if (!this.config.get('userThemesDisabled') && this.preferences.get('theme')) {
            values = this.preferences.get('themeParams');
        }

        if (!values) {
            values = this.config.get('themeParams');
        }

        if (values && (name in values)) {
            return values[name];
        }

        if ('default' in params[name]) {
            return params[name].default;
        }

        return null;
    }

    /**
     * @private
     * @param {string} name
     * @returns {*}
     */
    getMappedParam(name) {
        let mappedParams = this.getParam('mappedParams') || {};

        if (!(name in mappedParams)) {
            return null;
        }

        let mapped = mappedParams[name].param;
        let valueMap = mappedParams[name].valueMap;

        if (mapped && valueMap) {
            let key = this.getParam(mapped);

            return valueMap[key];
        }

        return null;
    }

    /**
     * @private
     * @returns {string}
     */
    getParentName() {
        return this.metadata.get(['themes', this.getName(), 'parent']) || 'Espo';
    }

    /**
     * Whether a current theme is different from a system default theme.
     *
     * @returns {boolean}
     */
    isUserTheme() {
        if (this.config.get('userThemesDisabled')) {
            return false;
        }

        let name = this.preferences.get('theme');

        if (!name || name === '') {
            return false;
        }

        return name !== this.config.get('theme');
    }
}

export default ThemeManager;
PK]K�3&U
U
dynamic-handler.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module dynamic-handler */

import {View as BullView} from 'bullbone';

/**
 * A dynamic handler. To be extended by a specific handler.
 */
class DynamicHandler {

    /**
     * @param {module:views/record/detail} recordView A record view.
     */
    constructor(recordView) {

        /**
         * A record view.
         *
         * @protected
         * @type {module:views/record/detail}
         */
        this.recordView = recordView;

        /**
         * A model.
         *
         * @protected
         * @type {module:model}
         */
        this.model = recordView.model;
    }

    /**
     * Initialization logic. To be extended.
     *
     * @protected
     */
    init() {}

    /**
     * Called on model change. To be extended.
     *
     * @protected
     * @param {module:views/record/detail} model A model.
     * @param {Object} o Options.
     */
    onChange(model, o) {}

    /**
     * Get a metadata.
     *
     * @protected
     * @returns {module:metadata}
     */
    getMetadata() {
        return this.recordView.getMetadata()
    }
}

DynamicHandler.extend = BullView.extend;

// noinspection JSUnusedGlobalSymbols
export default DynamicHandler;
PK]�i���app.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module app */

import $ from 'jquery';
import Backbone from 'backbone';
import {Events, View as BullView, Factory as BullFactory} from 'bullbone';
import Base64 from 'js-base64';
import Ui from 'ui';
import Utils from 'utils';
import AclManager from 'acl-manager';
import Cache from 'cache';
import Storage from 'storage';
import Settings from 'models/settings';
import Language from 'language';
import Metadata from 'metadata';
import FieldManager from 'field-manager';
import User from 'models/user';
import Preferences from 'models/preferences';
import ModelFactory from 'model-factory';
import CollectionFactory from 'collection-factory';
import BaseController from 'controllers/base';
import Router from 'router';
import DateTime from 'date-time';
import LayoutManager from 'layout-manager';
import ThemeManager from 'theme-manager';
import SessionStorage from 'session-storage';
import ViewHelper from 'view-helper';
import WebSocketManager from 'web-socket-manager';
import Ajax from 'ajax';
import NumberUtil from 'number-util';
import PageTitle from 'page-title';
import BroadcastChannel from 'broadcast-channel';

/**
 * A main application class.
 *
 * @mixes Bull.Events
 */
class App {

    /**
     * @param {module:app~Options} options Options.
     * @param {module:app~callback} callback A callback.
     */
    constructor(options, callback) {
        options = options || {};

        /**
         * An application ID.
         *
         * @private
         * @type {string}
         */
        this.id = options.id || 'espocrm-application-id';

        /**
         * Use cache.
         *
         * @private
         * @type {boolean}
         */
        this.useCache = options.useCache || this.useCache;

        this.apiUrl = options.apiUrl || this.apiUrl;

        /**
         * A base path.
         *
         * @type {string}
         */
        this.basePath = options.basePath || '';

        /**
         * A default ajax request timeout.
         *
         * @private
         * @type {Number}
         */
        this.ajaxTimeout = options.ajaxTimeout || 0;

        /**
         * A list of internal modules.
         *
         * @private
         * @type {string[]}
         */
        this.internalModuleList = options.internalModuleList || [];

        /**
         * A list of bundled modules.
         *
         * @private
         * @type {string[]}
         */
        this.bundledModuleList = options.bundledModuleList || [];

        this.appTimestamp = options.appTimestamp;

        this.initCache(options)
            .then(() => this.init(options, callback));

        this.initDomEventListeners();
    }

    /**
     * @private
     * @type {boolean}
     */
    useCache = false

    /**
     * @protected
     * @type {module:models/user}
     */
    user = null

    /**
     * @private
     * @type {module:models/preferences}
     */
    preferences = null

    /**
     * @protected
     * @type {module:models/settings}
     */
    settings = null

    /**
     * @private
     * @type {module:metadata}
     */
    metadata = null

    /**
     * @private
     * @type {module:language}
     */
    language = null

    /**
     * @private
     * @type {module:field-manager}
     */
    fieldManager = null

    /**
     * @private
     * @type {module:cache|null}
     */
    cache = null

    /**
     * @private
     * @type {module:storage|null}
     */
    storage = null

    /**
     * @private
     */
    loader = null

    /**
     * An API URL.
     *
     * @private
     */
    apiUrl = 'api/v1'

    /**
     * An auth credentials string.
     *
     * @private
     * @type {?string}
     */
    auth = null

    /**
     * Another user to login as.
     *
     * @private
     * @type {?string}
     */
    anotherUser = null

    /**
     * A base controller.
     *
     * @private
     * @type {module:controllers/base}
     */
    baseController = null

    /**
     * @private
     */
    controllers = null

    /**
     * @private
     * @type {module:router}
     */
    router = null

    /**
     * @private
     * @type {module:model-factory}
     */
    modelFactory = null

    /**
     * @private
     * @type {module:collection-factory}
     */
    collectionFactory = null

    /**
     * A view factory.
     *
     * @private
     * @type {Factory}
     */
    viewFactory = null

    /**
     * @type {function(string, function(View))}
     * @private
     */
    viewLoader = null

    /**
     * @private
     * @type {module:view-helper}
     */
    viewHelper = null

    /**
     * A body view.
     *
     * @protected
     * @type {string}
     */
    masterView = 'views/site/master'

    /**
     * @private
     * @type {Cache|null}
     */
    responseCache = null

    /**
     * @private
     * @type {module:broadcast-channel|null}
     */
    broadcastChannel = null

    /**
     * @private
     * @type {module:date-time|null}
     */
    dateTime = null

    /**
     * @private
     * @type {module:num-util|null}
     */
    numberUtil = null

    /**
     * @private
     * @type {module:web-socket-manager|null}
     */
    webSocketManager = null

    /**
     * An application timestamp. Used for asset cache busting and update detection.
     *
     * @private
     * @type {Number|null}
     */
    appTimestamp = null

    /** @private */
    started = false

    /** @private */
    aclName = 'acl'

    /**
     * @private
     * @param {module:app~Options} options
     * @return Promise
     */
    initCache(options) {
        if (!this.useCache) {
            return Promise.resolve();
        }

        let cacheTimestamp = options.cacheTimestamp || null;

        this.cache = new Cache(cacheTimestamp);

        let storedCacheTimestamp = this.cache.getCacheTimestamp();

        cacheTimestamp ?
            this.cache.handleActuality(cacheTimestamp) :
            this.cache.storeTimestamp();

        if (!window.caches) {
            return Promise.resolve();
        }

        return new Promise(resolve => {
            let deleteCache = !cacheTimestamp ||
                !storedCacheTimestamp ||
                cacheTimestamp !== storedCacheTimestamp;

            (
                deleteCache ?
                    caches.delete('espo') :
                    Promise.resolve()
            )
                .then(() => caches.open('espo'))
                .then(cache => {
                    this.responseCache = cache;

                    resolve();
                })
                .catch(() => {
                    console.error(`Could not open 'espo' cache.`);
                    resolve();
                });
        });
    }

    /**
     * @private
     * @param {module:app~Options} options
     * @param {function} [callback]
     */
    init(options, callback) {
        /** @type {Object.<string, *>} */
        this.appParams = {};
        this.controllers = {};

        /**
         * @type {Espo.loader}
         * @private
         */
        this.loader = Espo.loader;

        this.loader.setResponseCache(this.responseCache);

        if (this.useCache && !this.loader.getCacheTimestamp() && options.cacheTimestamp) {
            this.loader.setCacheTimestamp(options.cacheTimestamp);
        }

        this.storage = new Storage();
        this.sessionStorage = new SessionStorage();

        this.setupAjax();

        this.settings = new Settings(null);
        this.language = new Language(this.cache);
        this.metadata = new Metadata(this.cache);
        this.fieldManager = new FieldManager();

        this.initBroadcastChannel();

        Promise
            .all([
                this.settings.load(),
                this.language.loadDefault(),
                this.initTemplateBundles(),
            ])
            .then(() => {
                this.loader.setIsDeveloperMode(this.settings.get('isDeveloperMode'));

                this.user = new User();
                this.preferences = new Preferences();

                this.preferences.settings = this.settings;

                /** @type {module:acl-manager} */
                this.acl = this.createAclManager();

                this.fieldManager.acl = this.acl;

                this.themeManager = new ThemeManager(this.settings, this.preferences, this.metadata);
                this.modelFactory = new ModelFactory(this.metadata);
                this.collectionFactory = new CollectionFactory(this.modelFactory, this.settings, this.metadata);

                if (this.settings.get('useWebSocket')) {
                    this.webSocketManager = new WebSocketManager(this.settings);
                }

                this.initUtils();
                this.initView();
                this.initBaseController();

                callback.call(this, this);
            });
    }

    /**
     * Start the application.
     */
    start() {
        this.initAuth();

        this.started = true;

        if (!this.auth) {
            this.baseController.login();

            return;
        }

        this.initUserData(null, () => this.onAuth.call(this));
    }

    /**
     * @private
     * @param {boolean} [afterLogin]
     */
    onAuth(afterLogin) {
        this.metadata.load().then(() => {
            this.fieldManager.defs = this.metadata.get('fields');
            this.fieldManager.metadata = this.metadata;

            this.settings.defs = this.metadata.get('entityDefs.Settings') || {};
            this.user.defs = this.metadata.get('entityDefs.User');
            this.preferences.defs = this.metadata.get('entityDefs.Preferences');
            this.viewHelper.layoutManager.userId = this.user.id;

            if (this.themeManager.isUserTheme()) {
                this.loadStylesheet();
            }

            if (this.anotherUser) {
                this.viewHelper.webSocketManager = null;
                this.webSocketManager = null;
            }

            if (this.webSocketManager) {
                this.webSocketManager.connect(this.auth, this.user.id);
            }

            let promiseList = [];
            let aclImplementationClassMap = {};

            let clientDefs = this.metadata.get('clientDefs') || {};

            Object.keys(clientDefs).forEach(scope => {
                let o = clientDefs[scope];

                let implClassName = (o || {})[this.aclName];

                if (!implClassName) {
                    return;
                }

                promiseList.push(
                    new Promise(resolve => {
                        this.loader.require(implClassName, implClass => {
                            aclImplementationClassMap[scope] = implClass;

                            resolve();
                        });
                    })
                );
            });

            if (!this.themeManager.isApplied() && this.themeManager.isUserTheme()) {
                promiseList.push(
                    new Promise(resolve => {
                        const check = i => {
                            if (this.themeManager.isApplied() || i === 50) {
                                resolve();

                                return;
                            }

                            i = i || 0;

                            setTimeout(() => check(i + 1), 10);
                        }

                        check();
                    })
                );
            }

            Promise.all(promiseList)
                .then(() => {
                    this.acl.implementationClassMap = aclImplementationClassMap;

                    this.initRouter();
                });

            if (afterLogin) {
                this.broadcastChannel.postMessage('logged-in');
            }
        });
    }

    /**
     * @private
     */
    initRouter() {
        let routes = this.metadata.get(['app', 'clientRoutes']) || {};

        this.router = new Router({routes: routes});

        this.viewHelper.router = this.router;

        this.baseController.setRouter(this.router);

        this.router.confirmLeaveOutMessage = this.language.translate('confirmLeaveOutMessage', 'messages');
        this.router.confirmLeaveOutConfirmText = this.language.translate('Yes');
        this.router.confirmLeaveOutCancelText = this.language.translate('Cancel');

        this.router.on('routed', params => this.doAction(params));

        try {
            Backbone.history.start({root: window.location.pathname});
        }
        catch (e) {
            Backbone.history.loadUrl();
        }
    }

    /**
     * Do an action.
     *
     * @public
     * @param {{
     *   controller?: string,
     *   action: string,
     *   options?: Object.<string,*>,
     *   controllerClassName?: string,
     * }} params
     */
    doAction(params) {
        this.trigger('action', params);

        this.baseController.trigger('action');

        let callback = controller => {
            try {
                controller.doAction(params.action, params.options);

                this.trigger('action:done');
            }
            catch (e) {
                console.error(e);

                switch (e.name) {
                    case 'AccessDenied':
                        this.baseController.error403();

                        break;

                    case 'NotFound':
                        this.baseController.error404();

                        break;

                    default:
                        throw e;
                }
            }
        };

        if (params.controllerClassName) {
            this.createController(params.controllerClassName, null, callback);

            return;
        }

        this.getController(params.controller, callback);
    }

    /**
     * @private
     */
    initBaseController() {
        this.baseController = new BaseController({}, this.getControllerInjection());

        this.viewHelper.baseController = this.baseController;
    }

    /**
     * @private
     */
    getControllerInjection() {
        return {
            viewFactory: this.viewFactory,
            modelFactory: this.modelFactory,
            collectionFactory: this.collectionFactory,
            settings: this.settings,
            user: this.user,
            preferences: this.preferences,
            acl: this.acl,
            cache: this.cache,
            router: this.router,
            storage: this.storage,
            metadata: this.metadata,
            dateTime: this.dateTime,
            broadcastChannel: this.broadcastChannel,
            baseController: this.baseController,
        };
    }

    /**
     * @param {string} name
     * @param {function(module:controller): void} callback
     * @private
     */
    getController(name, callback) {
        if (!name) {
            callback(this.baseController);

            return;
        }

        if (name in this.controllers) {
            callback(this.controllers[name]);

            return;
        }

        try {
            let className = this.metadata.get(['clientDefs', name, 'controller']);

            if (!className) {
                let module = this.metadata.get(['scopes', name, 'module']);

                className = Utils.composeClassName(module, name, 'controllers');
            }

            this.createController(className, name, callback);
        }
        catch (e) {
            this.baseController.error404();
        }
    }

    /**
     * @private
     * @return {module:controller}
     */
    createController(className, name, callback) {
        Espo.loader.require(
            className,
            controllerClass => {
                let injections = this.getControllerInjection();

                let controller = new controllerClass(this.baseController.params, injections);

                controller.name = name;
                controller.masterView = this.masterView;

                this.controllers[name] = controller

                callback(controller);
            },
            () => this.baseController.error404()
        );
    }

    /**
     * @private
     */
    initUtils() {
        this.dateTime = new DateTime();
        this.modelFactory.dateTime = this.dateTime;
        this.dateTime.setSettingsAndPreferences(this.settings, this.preferences);
        this.numberUtil = new NumberUtil(this.settings, this.preferences);
    }

    /**
     * Create an acl-manager.
     *
     * @protected
     * @return {module:acl-manager}
     */
    createAclManager() {
        return new AclManager(this.user, null, this.settings.get('aclAllowDeleteCreated'));
    }

    /**
     * @private
     */
    initView() {
        let helper = this.viewHelper = new ViewHelper();

        helper.layoutManager = new LayoutManager(this.cache, this.id);
        helper.settings = this.settings;
        helper.config = this.settings;
        helper.user = this.user;
        helper.preferences = this.preferences;
        helper.acl = this.acl;
        helper.modelFactory = this.modelFactory;
        helper.collectionFactory = this.collectionFactory;
        helper.storage = this.storage;
        helper.sessionStorage = this.sessionStorage;
        helper.dateTime = this.dateTime;
        helper.language = this.language;
        helper.metadata = this.metadata;
        helper.fieldManager = this.fieldManager;
        helper.cache = this.cache;
        helper.themeManager = this.themeManager;
        helper.webSocketManager = this.webSocketManager;
        helper.numberUtil = this.numberUtil;
        helper.pageTitle = new PageTitle(this.settings);
        helper.basePath = this.basePath;
        helper.appParams = this.appParams;
        helper.broadcastChannel = this.broadcastChannel;

        this.viewLoader = (viewName, callback) => {
            this.loader.require(Utils.composeViewClassName(viewName), callback);
        };

        let internalModuleMap = {};

        const isModuleInternal = (module) => {
            if (!(module in internalModuleMap)) {
                internalModuleMap[module] = this.internalModuleList.indexOf(module) !== -1;
            }

            return internalModuleMap[module];
        };

        const getResourceInnerPath = (type, name) => {
            let path = null;

            switch (type) {
                case 'template':
                    if (~name.indexOf('.')) {
                        console.warn(name + ': template name should use slashes for a directory separator.');
                    }

                    path = 'res/templates/' + name.split('.').join('/') + '.tpl';

                    break;

                case 'layoutTemplate':
                    path = 'res/layout-types/' + name + '.tpl';

                    break;
            }

            return path;
        };

        const getResourcePath = (type, name) => {
            if (!name.includes(':')) {
                return 'client/' + getResourceInnerPath(type, name);
            }

            let [mod, path] = name.split(':');

            if (mod === 'custom') {
                return 'client/custom/' + getResourceInnerPath(type, path);
            }

            if (isModuleInternal(mod)) {
                return 'client/modules/' + mod + '/' + getResourceInnerPath(type, path);
            }

            return 'client/custom/modules/' + mod + '/' + getResourceInnerPath(type, path);
        };

        this.viewFactory = new BullFactory({
            defaultViewName: 'views/base',
            helper: helper,
            viewLoader: this.viewLoader,
            resources: {
                loaders: {
                    template: (name, callback) => {
                        let path = getResourcePath('template', name);

                        this.loader.require('res!' + path, callback);
                    },
                    layoutTemplate: (name, callback) => {
                        if (Espo.layoutTemplates && name in Espo.layoutTemplates) {
                            callback(Espo.layoutTemplates[name]);

                            return;
                        }

                        let path = getResourcePath('layoutTemplate', name);

                        this.loader.require('res!' + path, callback);
                    },
                },
            },
            preCompiledTemplates: Espo.preCompiledTemplates || {},
        });
    }

    /**
     * @public
     */
    initAuth() {
        this.auth = this.storage.get('user', 'auth') || null;
        this.anotherUser = this.storage.get('user', 'anotherUser') || null;

        this.baseController.on('login', data => {
            let userId = data.user.id;
            let userName = data.auth.userName;
            let token = data.auth.token;
            let anotherUser = data.auth.anotherUser || null;

            this.auth = Base64.encode(userName  + ':' + token);
            this.anotherUser = anotherUser;

            let lastUserId = this.storage.get('user', 'lastUserId');

            if (lastUserId !== userId) {
                this.metadata.clearCache();
                this.language.clearCache();
            }

            this.storage.set('user', 'auth', this.auth);
            this.storage.set('user', 'lastUserId', userId);
            this.storage.set('user', 'anotherUser', this.anotherUser);

            this.setCookieAuth(userName, token);

            this.initUserData(data, () => this.onAuth(true));
        });

        this.baseController.on('logout', () => this.logout());
    }

    /**
     * @private
     */
    logout(afterFail, silent) {
        let logoutWait = false;

        if (this.auth && !afterFail) {
            let arr = Base64.decode(this.auth).split(':');

            if (arr.length > 1) {
                logoutWait = this.appParams.logoutWait || false;

                Ajax.postRequest('App/destroyAuthToken', {token: arr[1]}, {resolveWithXhr: true})
                    .then(/** XMLHttpRequest */xhr => {
                        let redirectUrl = xhr.getResponseHeader('X-Logout-Redirect-Url');

                        if (redirectUrl) {
                            setTimeout(() => window.location.href = redirectUrl, 50);

                            return;
                        }

                        if (logoutWait) {
                            this.doAction({action: 'login'});
                        }
                    });
            }
        }

        if (this.webSocketManager) {
            this.webSocketManager.close();
        }

        silent = silent || afterFail &&
            this.auth &&
            this.auth !== this.storage.get('user', 'auth');

        this.auth = null;
        this.anotherUser = null;

        this.user.clear();
        this.preferences.clear();
        this.acl.clear();

        if (!silent) {
            this.storage.clear('user', 'auth');
            this.storage.clear('user', 'anotherUser');
        }

        let action = logoutWait ? 'logoutWait' : 'login';

        this.doAction({action: action});

        if (!silent) {
            this.unsetCookieAuth();
        }

        if (this.broadcastChannel.object) {
            if (!silent) {
                this.broadcastChannel.postMessage('logged-out');
            }
        }

        if (!silent) {
            this.sendLogoutRequest();
        }

        this.loadStylesheet();
    }

    /**
     * @private
     */
    sendLogoutRequest() {
        let xhr = new XMLHttpRequest;

        xhr.open('GET', this.basePath + this.apiUrl + '/');
        xhr.setRequestHeader('Authorization', 'Basic ' + Base64.encode('**logout:logout'));
        xhr.send('');
        xhr.abort();
    }

    /**
     * @private
     */
    loadStylesheet() {
        if (!this.metadata.get(['themes'])) {
            return;
        }

        let stylesheetPath = this.basePath + this.themeManager.getStylesheet();

        $('#main-stylesheet').attr('href', stylesheetPath);
    }

    /**
     * @private
     */
    setCookieAuth(username, token) {
        let date = new Date();

        date.setTime(date.getTime() + (1000 * 24 * 60 * 60 * 1000));

        document.cookie = 'auth-token=' + token + '; SameSite=Lax; expires=' + date.toUTCString() + '; path=/';
    }

    /**
     * @private
     */
    unsetCookieAuth() {
        document.cookie = 'auth-token' + '=; SameSite=Lax; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/';
    }

    /**
     * @private
     */
    initUserData(options, callback) {
        options = options || {};

        if (this.auth === null) {
            return;
        }

        new Promise(resolve => {
            if (options.user) {
                resolve(options);

                return;
            }

            this.requestUserData(data => {
                options = data;

                resolve(options);
            });
        })
            .then(options => {
                this.language.name = options.language;

                return this.language.load();
            })
            .then(() => {
                this.dateTime.setLanguage(this.language);

                let userData = options.user || null;
                let preferencesData = options.preferences || null;
                let aclData = options.acl || null;

                let settingData = options.settings || {};

                this.user.set(userData);
                this.preferences.set(preferencesData);

                this.settings.set(settingData);
                this.acl.set(aclData);

                for (let param in options.appParams) {
                    this.appParams[param] = options.appParams[param];
                }

                if (!this.auth) {
                    return;
                }

                let xhr = new XMLHttpRequest();

                xhr.open('GET', this.basePath + this.apiUrl + '/');
                xhr.setRequestHeader('Authorization', 'Basic ' + this.auth);

                xhr.onreadystatechange = () => {
                    if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
                        let arr = Base64.decode(this.auth).split(':');

                        this.setCookieAuth(arr[0], arr[1]);

                        callback();
                    }

                    if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 401) {
                        Ui.error('Auth error');
                    }
                };

                xhr.send('');
            });
    }

    /**
     * @private
     */
    requestUserData(callback) {
        Ajax.getRequest('App/user', {}, {appStart: true})
            .then(callback);
    }

    /**
     * @private
     */
    setupAjax() {
        /**
         * @param {XMLHttpRequest} xhr
         * @param {Object.<string, *>} options
         */
        const beforeSend = (xhr, options) => {
            if (this.auth !== null && !options.login) {
                xhr.setRequestHeader('Authorization', 'Basic ' + this.auth);
                xhr.setRequestHeader('Espo-Authorization', this.auth);
                xhr.setRequestHeader('Espo-Authorization-By-Token', 'true');
            }

            if (this.anotherUser !== null && !options.login) {
                xhr.setRequestHeader('X-Another-User', this.anotherUser);
            }
        };

        let appTimestampChangeProcessed = false;

        /**
         * @param {XMLHttpRequest} xhr
         * @param {Object.<string, *>} options
         */
        const onSuccess = (xhr, options) => {
            let appTimestampHeader = xhr.getResponseHeader('X-App-Timestamp');

            if (!appTimestampHeader || appTimestampChangeProcessed) {
                return;
            }

            let appTimestamp = parseInt(appTimestampHeader);

            // noinspection JSUnresolvedReference
            let bypassAppReload = options.bypassAppReload;

            if (
                this.appTimestamp &&
                // this.appTimestamp is set to current time if cache disabled.
                appTimestamp > this.appTimestamp &&
                !bypassAppReload
            ) {
                appTimestampChangeProcessed = true;

                Ui
                    .confirm(
                        this.language.translate('confirmAppRefresh', 'messages'),
                        {
                            confirmText: this.language.translate('Refresh'),
                            cancelText: this.language.translate('Cancel'),
                            backdrop: 'static',
                            confirmStyle: 'success',
                        }
                    )
                    .then(() => {
                        window.location.reload();

                        if (this.broadcastChannel) {
                            this.broadcastChannel.postMessage('reload');
                        }
                    });
            }
        };

        /**
         * @param {module:ajax.Xhr} xhr
         * @param {Object.<string, *>} options
         */
        const onError = (xhr, options) => {
            setTimeout(() => {
                if (xhr.errorIsHandled) {
                    return;
                }

                switch (xhr.status) {
                    case 200:
                        Ui.error(this.language.translate('Bad server response'));

                        console.error('Bad server response: ' + xhr.responseText);

                        break;

                    case 401:
                        // noinspection JSUnresolvedReference
                        if (options.login) {
                            break;
                        }

                        if (this.auth && this.router && !this.router.confirmLeaveOut) {
                            this.logout(true);

                            break;
                        }

                        if (this.auth && this.router && this.router.confirmLeaveOut) {
                            Ui.error(this.language.translate('loggedOutLeaveOut', 'messages'), true);

                            this.router.trigger('logout');

                            break;
                        }

                        if (this.auth) {
                            // noinspection JSUnresolvedReference
                            let silent = !options.appStart;

                            this.logout(true, silent);
                        }

                        console.error('Error 401: Unauthorized.');

                        break;

                    case 403:
                        // noinspection JSUnresolvedReference
                        if (options.main) {
                            this.baseController.error403();

                            break;
                        }

                        this._processErrorAlert(xhr, 'Access denied');

                        break;

                    case 400:
                        this._processErrorAlert(xhr, 'Bad request');

                        break;

                    case 404:
                        // noinspection JSUnresolvedReference
                        if (options.main) {
                            this.baseController.error404();

                            break
                        }

                        this._processErrorAlert(xhr, 'Not found', true);

                        break;

                    default:
                        this._processErrorAlert(xhr, null);
                }

                let statusReason = xhr.getResponseHeader('X-Status-Reason');

                if (statusReason) {
                    console.error('Server side error ' + xhr.status + ': ' + statusReason);
                }
            }, 0);
        };

        const onTimeout = () => {
            Ui.error(this.language.translate('Timeout'), true);
        };

        Ajax.configure({
            apiUrl: this.basePath + this.apiUrl,
            timeout: this.ajaxTimeout,
            beforeSend: beforeSend,
            onSuccess: onSuccess,
            onError: onError,
            onTimeout: onTimeout,
        });

        // For backward compatibility.
        // @todo Remove in v9.0.
        $.ajaxSetup({
            beforeSend: (xhr, options) => {
                if (!options.url || !options.url.includes('q=')) {
                    console.warn(`$.ajax is deprecated, support will be removed in v9.0. Use Espo.Ajax instead.`);
                }

                // noinspection JSUnresolvedReference
                if (!options.local && this.apiUrl) {
                    options.url = Utils.trimSlash(this.apiUrl) + '/' + options.url;
                }

                // noinspection JSUnresolvedReference
                if (!options.local && this.basePath !== '') {
                    options.url = this.basePath + options.url;
                }

                if (this.auth !== null) {
                    xhr.setRequestHeader('Authorization', 'Basic ' + this.auth);
                    xhr.setRequestHeader('Espo-Authorization', this.auth);
                    xhr.setRequestHeader('Espo-Authorization-By-Token', 'true');
                }

                if (this.anotherUser !== null) {
                    xhr.setRequestHeader('X-Another-User', this.anotherUser);
                }
            },
            dataType: 'json',
            timeout: this.ajaxTimeout,
            contentType: 'application/json',
        });
    }

    /**
     * @private
     */
    _processErrorAlert(xhr, label, noDetail) {
        let msg = this.language.translate('Error') + ' ' + xhr.status;

        if (label) {
            msg += ': ' + this.language.translate(label);
        }

        let obj = {
            msg: msg,
            closeButton: false,
        };

        let isMessageDone = false;

        if (noDetail) {
            isMessageDone = true;
        }

        if (!isMessageDone && xhr.responseText && xhr.responseText[0] === '{') {
            /** @type {Object.<string, *>|null} */
            let data = null;

            try {
                data = JSON.parse(xhr.responseText);
            }
            catch (e) {}

            if (data && data.messageTranslation && data.messageTranslation.label) {
                let msgDetail = this.language.translate(
                    data.messageTranslation.label,
                    'messages',
                    data.messageTranslation.scope
                );

                let msgData = data.messageTranslation.data || {};

                for (let key in msgData) {
                    msgDetail = msgDetail.replace('{' + key + '}', msgData[key]);
                }

                obj.msg += '\n' + msgDetail;
                obj.closeButton = true;

                isMessageDone = true;
            }
        }

        if (!isMessageDone) {
            let statusReason = xhr.getResponseHeader('X-Status-Reason');

            if (statusReason) {
                obj.msg += '\n' + statusReason;
                obj.closeButton = true;
            }
        }

        Ui.error(obj.msg, obj.closeButton);
    }

    /**
     * @private
     */
    initBroadcastChannel() {
        this.broadcastChannel = new BroadcastChannel();

        this.broadcastChannel.subscribe(event => {
            if (!this.auth && this.started) {
                if (event.data === 'logged-in') {
                    // This works if the same instance opened in different tabs.
                    // This does not work for different instances on the same domain
                    // which may be the case in dev environment.
                    window.location.reload();
                }

                return;
            }

            if (event.data === 'update:all') {
                this.metadata.loadSkipCache();
                this.settings.load();
                this.language.loadSkipCache();
                this.viewHelper.layoutManager.clearLoadedData();

                return;
            }

            if (event.data === 'update:metadata') {
                this.metadata.loadSkipCache();

                return;
            }

            if (event.data === 'update:config') {
                this.settings.load();

                return;
            }

            if (event.data === 'update:language') {
                this.language.loadSkipCache();

                return;
            }

            if (event.data === 'update:layout') {
                this.viewHelper.layoutManager.clearLoadedData();

                return;
            }

            if (event.data === 'reload') {
                window.location.reload();

                return;
            }

            if (event.data === 'logged-out' && this.started) {
                if (this.auth && this.router.confirmLeaveOut) {
                    Ui.error(this.language.translate('loggedOutLeaveOut', 'messages'), true);

                    this.router.trigger('logout');

                    return;
                }

                this.logout(true);
            }
        });
    }

    /**
     * @private
     */
    initDomEventListeners() {
        $(document).on('keydown.espo.button', e => {
            if (
                e.code !== 'Enter' ||
                e.target.tagName !== 'A' ||
                e.target.getAttribute('role') !== 'button' ||
                e.target.getAttribute('href') ||
                e.ctrlKey ||
                e.altKey ||
                e.metaKey
            ) {
                return;
            }

            $(e.target).click();

            e.preventDefault();
        });
    }

    /**
     * @private
     * @return {Promise}
     */
    initTemplateBundles() {
        if (!this.responseCache) {
            return Promise.resolve();
        }

        const key = 'templateBundlesCached';

        if (this.cache.get('app', key)) {
            return Promise.resolve();
        }

        let files = ['client/lib/templates.tpl'];

        this.bundledModuleList.forEach(mod => {
            let file = this.internalModuleList.includes(mod) ?
                `client/modules/${mod}/lib/templates.tpl` :
                `client/custom/modules/${mod}/lib/templates.tpl`;

            files.push(file);
        });

        let baseUrl = Utils.obtainBaseUrl();
        let timestamp = this.loader.getCacheTimestamp();

        let promiseList = files.map(file => {
            let url = new URL(baseUrl + this.basePath + file);
            url.searchParams.append('t', this.appTimestamp);

            return new Promise(resolve => {
                fetch(url)
                    .then(response => {
                        if (!response.ok) {
                            console.error(`Could not fetch ${url}.`);
                            resolve();

                            return;
                        }

                        let promiseList = [];

                        response.text().then(text => {
                            let index = text.indexOf('\n');

                            if (index <= 0) {
                                resolve();

                                return;
                            }

                            let delimiter = text.slice(0, index + 1);
                            text = text.slice(index + 1);

                            text.split(delimiter).forEach(item => {
                                let index = item.indexOf('\n');

                                let file = item.slice(0, index);
                                let content = item.slice(index + 1);

                                let url = baseUrl + this.basePath + 'client/' + file;

                                let urlObj = new URL(url);
                                urlObj.searchParams.append('r', timestamp);

                                promiseList.push(
                                    this.responseCache.put(urlObj, new Response(content))
                                );
                            });
                        });

                        Promise.all(promiseList).then(() => resolve());
                    });
            });
        });

        return Promise.all(promiseList)
            .then(() => {
                this.cache.set('app', key, true);
            });
    }
}

/**
 * @callback module:app~callback
 * @param {App} app A created application instance.
 */

/**
 * Application options.
 *
 * @typedef {Object} module:app~Options
 * @property {string} [id] An application ID.
 * @property {string} [basePath] A base path.
 * @property {boolean} [useCache] Use cache.
 * @property {string} [apiUrl] An API URL.
 * @property {Number} [ajaxTimeout] A default ajax request timeout.
 * @property {string} [internalModuleList] A list of internal modules.
 *   Internal modules located in the `client/modules` directory.
 * @property {string} [bundledModuleList] A list of bundled modules.
 * @property {Number|null} [cacheTimestamp] A cache timestamp.
 * @property {Number|null} [appTimestamp] An application timestamp.
 */

Object.assign(App.prototype, Events);

App.extend = BullView.extend;

export default App;
PK]H��Gll
exceptions.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module exceptions */

Espo.Exceptions = Espo.Exceptions || {};

/**
 * An access denied exception.
 *
 * @param {string} [message] A message.
 * @class
 */
Espo.Exceptions.AccessDenied = function (message) {
    this.message = message;

    Error.apply(this, arguments);
};

Espo.Exceptions.AccessDenied.prototype = new Error();
Espo.Exceptions.AccessDenied.prototype.name = 'AccessDenied';

/**
 * A not found exception.
 *
 * @param {string} [message] A message.
 * @class
 */
Espo.Exceptions.NotFound = function (message) {
    this.message = message;

    Error.apply(this, arguments);
};

Espo.Exceptions.NotFound.prototype = new Error();
Espo.Exceptions.NotFound.prototype.name = 'NotFound';

export default Espo.Exceptions;
PK]�|��D�D	router.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module router */

import Backbone from 'backbone';

/**
 * On route.
 *
 * @event Backbone.Router#route
 * @param {string} name A route name.
 * @param {any[]} args Arguments.
 */

/**
 * After dispatch.
 *
 * @event module:router#routed
 * @param {{
 *   controller: string,
 *   action:string,
 *   options: Object.<string,*>,
 * }} data A route data.
 */

/**
 * Subscribe.
 *
 * @function on
 * @memberof module:router#
 * @param {string} event An event.
 * @param {function(*): void} callback A callback.
 */

/**
 * Subscribe once.
 *
 * @function once
 * @memberof module:router#
 * @param {string} event An event.
 * @param {function(): void} callback A callback.
 */

/**
 * Unsubscribe.
 *
 * @function off
 * @memberof module:router#
 * @param {string} event An event.
 */

/**
 * Trigger an event.
 *
 * @function trigger
 * @memberof module:router#
 * @param {string} event An event.
 */

// noinspection JSUnusedGlobalSymbols
/**
 * A router.
 *
 * @class
 * @mixes Espo.Events
 */
const Router = Backbone.Router.extend(/** @lends Router# */ {

    /**
     * @private
     */
    routeList: [
        {
            route: "clearCache",
            resolution: "clearCache"
        },
        {
            route: ":controller/view/:id/:options",
            resolution: "view"
        },
        {
            route: ":controller/view/:id",
            resolution: "view"
        },
        {
            route: ":controller/edit/:id/:options",
            resolution: "edit"
        },
        {
            route: ":controller/edit/:id",
            resolution: "edit"
        },
        {
            route: ":controller/create",
            resolution: "create"
        },
        {
            route: ":controller/related/:id/:link",
            resolution: "related"
        },
        {
            route: ":controller/:action/:options",
            resolution: "action",
            order: 100
        },
        {
            route: ":controller/:action",
            resolution: "action",
            order: 200
        },
        {
            route: ":controller",
            resolution: "defaultAction",
            order: 300
        },
        {
            route: "*actions",
            resolution: "home",
            order: 500
        },
    ],

    /**
     * @private
     */
    _bindRoutes: function() {},

    /**
     * @private
     */
    setupRoutes: function () {
        this.routeParams = {};

        if (this.options.routes) {
            let routeList = [];

            Object.keys(this.options.routes).forEach(route => {
                let item = this.options.routes[route];

                routeList.push({
                    route: route,
                    resolution: item.resolution || 'defaultRoute',
                    order: item.order || 0
                });

                this.routeParams[route] = item.params || {};
            });

            this.routeList = Espo.Utils.clone(this.routeList);

            routeList.forEach(item => {
                this.routeList.push(item);
            });

            this.routeList = this.routeList.sort((v1, v2) => {
                return (v1.order || 0) - (v2.order || 0);
            });
        }

        this.routeList.reverse().forEach(item => {
            this.route(item.route, item.resolution);
        });
    },

    /**
     * @private
     */
    _last: null,

    /**
     * Whether a confirm-leave-out was set.
     *
     * @public
     * @type {boolean}
     */
    confirmLeaveOut: false,

    /**
     * Whether back has been processed.
     *
     * @public
     * @type {boolean}
     */
    backProcessed: false,

    /**
     * @type {string}
     * @internal
     */
    confirmLeaveOutMessage: 'Are you sure?',

    /**
     * @type {string}
     * @internal
     */
    confirmLeaveOutConfirmText: 'Yes',

    /**
     * @type {string}
     * @internal
     */
    confirmLeaveOutCancelText: 'No',

    /**
     * @private
     */
    initialize: function (options) {
        this.options = options || {};
        this.setupRoutes();

        this.history = [];

        let hashHistory = [window.location.hash];

        window.addEventListener('hashchange', () => {
            let hash = window.location.hash

            if (
                hashHistory.length > 1 &&
                hashHistory[hashHistory.length - 2] === hash
            ) {
                hashHistory = hashHistory.slice(0, -1);

                this.backProcessed = true;
                setTimeout(() => this.backProcessed = false, 50);

                return;
            }

            hashHistory.push(hash);
        });

        this.on('route', () => {
            this.history.push(Backbone.history.fragment);
        });

        window.addEventListener('beforeunload', (e) => {
            e = e || window.event;

            if (this.confirmLeaveOut) {
                e.preventDefault();

                e.returnValue = this.confirmLeaveOutMessage;

                return this.confirmLeaveOutMessage;
            }
        });
    },

    /**
     * Get a current URL.
     *
     * @returns {string}
     */
    getCurrentUrl: function () {
        return '#' + Backbone.history.fragment;
    },

    /**
     * @callback module:router~checkConfirmLeaveOutCallback
     */

    /**
     * Process confirm-leave-out.
     *
     * @param {module:router~checkConfirmLeaveOutCallback} callback Proceed if confirmed.
     * @param {Object|null} [context] A context.
     * @param {boolean} [navigateBack] To navigate back if not confirmed.
     */
    checkConfirmLeaveOut: function (callback, context, navigateBack) {
        if (this.confirmLeaveOutDisplayed) {
            this.navigateBack({trigger: false});

            this.confirmLeaveOutCanceled = true;

            return;
        }

        context = context || this;

        if (this.confirmLeaveOut) {
            this.confirmLeaveOutDisplayed = true;
            this.confirmLeaveOutCanceled = false;

            Espo.Ui.confirm(
                this.confirmLeaveOutMessage,
                {
                    confirmText: this.confirmLeaveOutConfirmText,
                    cancelText: this.confirmLeaveOutCancelText,
                    backdrop: true,
                    cancelCallback: () => {
                        this.confirmLeaveOutDisplayed = false;

                        if (navigateBack) {
                            this.navigateBack({trigger: false});
                        }
                    },
                },
                () => {
                    this.confirmLeaveOutDisplayed = false;
                    this.confirmLeaveOut = false;

                    if (!this.confirmLeaveOutCanceled) {
                        callback.call(context);
                    }
                }
            );

            return;
        }

        callback.call(context);
    },

    /**
     * @private
     */
    route: function (route, name/*, callback*/) {
        let routeOriginal = route;

        if (!_.isRegExp(route)) {
            route = this._routeToRegExp(route);
        }

        let callback;

        // @todo Revise.
        /*if (_.isFunction(name)) {
            callback = name;
            name = '';
        }*/

        /*if (!callback) {
            callback = this['_' + name];
        }*/
        callback = this['_' + name];

        let router = this;

        Backbone.history.route(route, function (fragment) {
            let args = router._extractParameters(route, fragment);

            let options = {};

            if (name === 'defaultRoute') {
                let keyList = [];

                routeOriginal.split('/').forEach(key => {
                    if (key && key.indexOf(':') === 0) {
                        keyList.push(key.substr(1));
                    }
                });

                keyList.forEach((key, i) => {
                    options[key] = args[i];
                });
            }

            // @todo Revise.
            router.execute(callback, args, name, routeOriginal, options);
            //if (router.execute(callback, args, name, routeOriginal, options) !== false) {
                router.trigger.apply(router, ['route:' + name].concat(args));
                router.trigger('route', name, args);
                Backbone.history.trigger('route', router, name, args);
            //}
        });

        return this;
    },

    /**
     * @private
     */
    execute: function (callback, args, name, routeOriginal, options) {
        this.checkConfirmLeaveOut(() => {
            if (name === 'defaultRoute') {
                this._defaultRoute(this.routeParams[routeOriginal], options);

                return;
            }

            Backbone.Router.prototype.execute.call(this, callback, args, name);
        }, null, true);
    },

    /**
     * Navigate.
     *
     * @param {string} fragment An URL fragment.
     * @param {{trigger?: boolean, replace?: boolean}} [options] Options: trigger, replace.
     */
    navigate: function (fragment, options) {
        this.history.push(fragment);

        return Backbone.Router.prototype.navigate.call(this, fragment, options);
    },

    /**
     * Navigate back.
     *
     * @param {Object} [options] Options: trigger, replace.
     */
    navigateBack: function (options) {
        let url;

        if (this.history.length > 1) {
            url = this.history[this.history.length - 2];
        }
        else {
            url = this.history[0];
        }

        this.navigate(url, options);
    },

    /**
     * @private
     */
    _parseOptionsParams: function (string) {
        if (!string) {
            return {};
        }

        if (string.indexOf('&') === -1 && string.indexOf('=') === -1) {
            return string;
        }

        let options = {};

        if (typeof string !== 'undefined') {
            string.split('&').forEach(item => {
                let p = item.split('=');

                options[p[0]] = true;

                if (p.length > 1) {
                    options[p[0]] = p[1];
                }
            });
        }

        return options;
    },

    /**
     * @private
     */
    _defaultRoute: function (params, options) {
        let controller = params.controller || options.controller;
        let action = params.action || options.action;

        this.dispatch(controller, action, options);
    },

    /**
     * @private
     */
    _record: function (controller, action, id, options) {
        options = this._parseOptionsParams(options);

        options.id = id;

        this.dispatch(controller, action, options);
    },

    /**
     * @private
     */
    _view: function (controller, id, options) {
        this._record(controller, 'view', id, options);
    },

    /**
     * @private
     */
    _edit: function (controller, id, options) {
        this._record(controller, 'edit', id, options);
    },

    /**
     * @private
     */
    _related: function (controller, id, link, options) {
        options = this._parseOptionsParams(options);

        options.id = id;
        options.link = link;

        this.dispatch(controller, 'related', options);
    },

    /**
     * @private
     */
    _create: function (controller, options) {
        this._record(controller, 'create', null, options);
    },

    /**
     * @private
     */
    _action: function (controller, action, options) {
        this.dispatch(controller, action, this._parseOptionsParams(options));
    },

    /**
     * @private
     */
    _defaultAction: function (controller) {
        this.dispatch(controller, null);
    },

    /**
     * @private
     */
    _home: function () {
        this.dispatch('Home', null);
    },

    /**
     * @private
     */
    _clearCache: function () {
        this.dispatch(null, 'clearCache');
    },

    /**
     * Process `logout` route.
     */
    logout: function () {
        this.dispatch(null, 'logout');

        this.navigate('', {trigger: false});
    },

    /**
     * Dispatch a controller action.
     *
     * @param {string|null} [controller] A controller.
     * @param {string|null} [action] An action.
     * @param {Object} [options] Options.
     * @fires module:router#routed
     */
    dispatch: function (controller, action, options) {
        let o = {
            controller: controller,
            action: action,
            options: options,
        };

        this._last = o;

        this.trigger('routed', o);
    },

    /**
     * Get the last route data.
     *
     * @returns {Object}
     */
    getLast: function () {
        return this._last;
    },
});

export default Router;

function isIOS9UIWebView() {
    let userAgent = window.navigator.userAgent;

    return /(iPhone|iPad|iPod).* OS 9_\d/.test(userAgent) && !/Version\/9\./.test(userAgent);
}

// Fixes issue that navigate with {trigger: false} fired
// route change if there's a whitespace character.
Backbone.history.getHash = function (window) {
    let match = (window || this).location.href.match(/#(.*)$/);

    return match ? this.decodeFragment(match[1]) : '';
};

// Override `backbone.history.loadUrl()` and `backbone.history.navigate()`
// to fix the navigation issue (`location.hash` not changed immediately) on iOS9.
if (isIOS9UIWebView()) {
    Backbone.history.loadUrl = function (fragment, oldHash) {
        fragment = this.fragment = this.getFragment(fragment);

        return _.any(this.handlers, function (handler) {
            if (handler.route.test(fragment)) {
                function runCallback() {
                    handler.callback(fragment);
                }

                function wait() {
                    if (oldHash === location.hash) {
                        window.setTimeout(wait, 50);
                    }
                    else {
                        runCallback();
                    }
                }

                wait();

                return true;
            }
        });
    };

    Backbone.history.navigate = function (fragment, options) {
        let pathStripper = /#.*$/;

        if (!Backbone.History.started) {
            return false;
        }

        if (!options || options === true) {
            options = {
                trigger: !!options
            };
        }

        let url = this.root + '#' + (fragment = this.getFragment(fragment || ''));

        fragment = fragment.replace(pathStripper, '');

        if (this.fragment === fragment) {
            return;
        }

        this.fragment = fragment;

        if (fragment === '' && url !== '/') {
            url = url.slice(0, -1);
        }

        let oldHash = location.hash;

        if (this._hasPushState) {
            this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);
        }
        else if (this._wantsHashChange) {
            this._updateHash(this.location, fragment, options.replace);

            if (
                this.iframe &&
                (fragment !== this.getFragment(this.getHash(this.iframe)))
            ) {
                if (!options.replace) {
                    this.iframe.document.open().close();
                }

                this._updateHash(this.iframe.location, fragment, options.replace);
            }
        }
        else {
            return this.location.assign(url);
        }

        if (options.trigger) {
            return this.loadUrl(fragment, oldHash);
        }
    };
}
PK]������
acl-portal.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module acl-portal */

import Acl from 'acl';

/**
 * Internal class for portal access checking. Can be extended to customize access checking
 * for a specific scope.
 */
class AclPortal extends Acl {

    /** @inheritDoc */
    checkScope(data, action, precise, entityAccessData) {
        entityAccessData = entityAccessData || {};

        let inAccount = entityAccessData.inAccount;
        let isOwnContact = entityAccessData.isOwnContact;
        let isOwner = entityAccessData.isOwner;

        if (this.getUser().isAdmin()) {
            return true;
        }

        if (data === false) {
            return false;
        }

        if (data === true) {
            return true;
        }

        if (typeof data === 'string') {
            return true;
        }

        if (data === null) {
            return false;
        }

        action = action || null;

        if (action === null) {
            return true;
        }

        if (!(action in data)) {
            return false;
        }

        var value = data[action];

        if (value === 'all') {
            return true;
        }

        if (value === 'yes') {
            return true;
        }

        if (value === 'no') {
            return false;
        }

        if (typeof isOwner === 'undefined') {
            return true;
        }

        if (isOwner) {
            if (value === 'own' || value === 'account' || value === 'contact') {
                return true;
            }
        }

        var result = false;

        if (value === 'account') {
            result = inAccount;
            if (inAccount === null) {
                if (precise) {
                    result = null;
                }
                else {
                    return true;
                }
            }
            else if (inAccount) {
                return true;
            }
        }

        if (value === 'contact') {
            result = isOwnContact;

            if (isOwnContact === null) {
                if (precise) {
                    result = null;
                }
                else {
                    return true;
                }
            }
            else if (isOwnContact) {
                return true;
            }
        }

        if (isOwner === null) {
            if (precise) {
                result = null;
            }
            else {
                return true;
            }
        }

        return result;
    }

    /** @inheritDoc */
    checkModel(model, data, action, precise) {
        if (this.getUser().isAdmin()) {
            return true;
        }

        let entityAccessData = {
            isOwner: this.checkIsOwner(model),
            inAccount: this.checkInAccount(model),
            isOwnContact: this.checkIsOwnContact(model),
        };

        return this.checkScope(data, action, precise, entityAccessData);
    }

    /** @inheritDoc */
    checkIsOwner(model) {
        if (model.hasField('createdBy')) {
            if (this.getUser().id === model.get('createdById')) {
                return true;
            }
        }

        return false;
    }

    /**
     * Check if a user in an account of a model.
     *
     * @param {module:model} model A model.
     * @returns {boolean|null} True if in an account, null if not clear.
     */
    checkInAccount(model) {
        let accountIdList = this.getUser().getLinkMultipleIdList('accounts');

        if (!accountIdList.length) {
            return false;
        }

        if (model.hasField('account')) {
            if (model.get('accountId')) {
                if (~accountIdList.indexOf(model.get('accountId'))) {
                    return true;
                }
            }
        }

        var result = false;

        if (model.hasField('accounts') && model.hasLink('accounts')) {
            if (!model.has('accountsIds')) {
                result = null;
            }

            (model.getLinkMultipleIdList('accounts')).forEach(id => {
                if (~accountIdList.indexOf(id)) {
                    result = true;
                }
            });
        }

        if (model.hasField('parent') && model.hasLink('parent')) {
            if (model.get('parentType') === 'Account') {
                if (!accountIdList.indexOf(model.get('parentId'))) {
                    return true;
                }
            }
        }

        if (result === false) {
            if (!model.hasField('accounts') && model.hasLink('accounts')) {
                return true;
            }
        }

        return result;
    }

    /**
     * Check if a user is a contact-owner to a model.
     *
     * @param {module:model} model A model.
     * @returns {boolean|null} True if in a contact-owner, null if not clear.
     */
    checkIsOwnContact(model) {
        let contactId = this.getUser().get('contactId');

        if (!contactId) {
            return false;
        }

        if (model.hasField('contact')) {
            if (model.get('contactId')) {
                if (contactId === model.get('contactId')) {
                    return true;
                }
            }
        }

        let result = false;

        if (model.hasField('contacts') && model.hasLink('contacts')) {
            if (!model.has('contactsIds')) {
                result = null;
            }

            (model.getLinkMultipleIdList('contacts')).forEach(id => {
                if (contactId === id) {
                    result = true;
                }
            });
        }

        if (model.hasField('parent') && model.hasLink('parent')) {
            if (model.get('parentType') === 'Contact' && model.get('parentId') === contactId) {
                return true;
            }
        }

        if (result === false) {
            if (!model.hasField('contacts') && model.hasLink('contacts')) {
                return true;
            }
        }

        return result;
    }
}

export default AclPortal;
PK]�����model-factory.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module model-factory */

/**
 * A model factory.
 */
class ModelFactory {
    /**
     * @param {module:metadata} metadata
     */
    constructor (metadata) {
        this.metadata = metadata;
    }

    /**
     * Used by default value expressions.
     * @public
     * @type {module:date-time|null}
     * @internal
     */
    dateTime = null

    /**
     * Create a model.
     *
     * @param {string} entityType An entity type.
     * @param {Function} [callback] Deprecated.
     * @param {Object} [context] Deprecated.
     * @returns {Promise<module:model>}
     */
    create(entityType, callback, context) {
        return new Promise(resolve => {
            context = context || this;

            this.getSeed(entityType, Seed => {
                let model = new Seed({}, {
                    entityType: entityType,
                    defs: this.metadata.get(['entityDefs', entityType]) || {},
                    dateTime: this.dateTime,
                });

                if (callback) {
                    callback.call(context, model);
                }

                resolve(model);
            });
        });
    }

    /**
     * Get a class.
     *
     * @param {string} entityType An entity type.
     * @param {function(module:model): void} callback A callback.
     * @public
     */
    getSeed(entityType, callback) {
        let className = this.metadata.get(['clientDefs', entityType, 'model']) || 'model';

        Espo.loader.require(className, modelClass => callback(modelClass));
    }
}

export default ModelFactory;
PK]�9n9nview-helper.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module view-helper */

import {marked} from 'marked';
import DOMPurify from 'dompurify';
import Handlebars from 'handlebars';

/**
 * A view helper.
 */
class ViewHelper {

    constructor() {
        this._registerHandlebarsHelpers();

        /** @private */
        this.mdBeforeList = [
            {
                regex: /&#x60;&#x60;&#x60;\n?([\s\S]*?)&#x60;&#x60;&#x60;/g,
                value: function (s, string) {
                    return '<pre><code>' +string.replace(/\*/g, '&#42;').replace(/~/g, '&#126;') +
                        '</code></pre>';
                }
            },
            {
                regex: /&#x60;([\s\S]*?)&#x60;/g,
                value: function (s, string) {
                    return '<code>' + string.replace(/\*/g, '&#42;').replace(/~/g, '&#126;') + '</code>';
                }
            }
        ];

        marked.setOptions({
            breaks: true,
            tables: false,
        });

        DOMPurify.addHook('beforeSanitizeAttributes', function (node) {
            if (node instanceof HTMLAnchorElement) {
                if (node.getAttribute('target')) {
                    node.targetBlank = true;
                }
                else {
                    node.targetBlank = false;
                }
            }
        });

        DOMPurify.addHook('afterSanitizeAttributes', function (node) {
            if (node instanceof HTMLAnchorElement) {
                const href = node.getAttribute('href');

                if (href && !href.startsWith('#')) {
                    node.setAttribute('rel', 'noopener noreferrer');
                }

                if (node.targetBlank) {
                    node.setAttribute('target', '_blank');
                    node.setAttribute('rel', 'noopener noreferrer');
                }
            }
        });
    }

    /**
     * A layout manager.
     *
     * @type {module:layout-manager}
     */
    layoutManager = null

    /**
     * A config.
     *
     * @type {module:models/settings}
     */
    settings = null

    /**
     * A config.
     *
     * @type {module:models/settings}
     */
    config = null

    /**
     * A current user.
     *
     * @type {module:models/user}
     */
    user = null

    /**
     * A preferences.
     *
     * @type {module:models/preferences}
     */
    preferences = null

    /**
     * An ACL manager.
     *
     * @type {module:acl-manager}
     */
    acl = null

    /**
     * A model factory.
     *
     * @type {module:model-factory}
     */
    modelFactory = null

    /**
     * A collection factory.
     *
     * @type {module:collection-factory}
     */
    collectionFactory = null

    /**
     * A router.
     *
     * @type {module:router}
     */
    router = null

    /**
     * A storage.
     *
     * @type {module:storage}
     */
    storage = null

    /**
     * A session storage.
     *
     * @type {module:session-storage}
     */
    sessionStorage = null

    /**
     * A date-time util.
     *
     * @type {module:date-time}
     */
    dateTime = null

    /**
     * A language.
     *
     * @type {module:language}
     */
    language = null

    /**
     * A metadata.
     *
     * @type {module:metadata}
     */
    metadata = null

    /**
     * A field-manager util.
     *
     * @type {module:field-manager}
     */
    fieldManager = null

    /**
     * A cache.
     *
     * @type {module:cache}
     */
    cache = null

    /**
     * A theme manager.
     *
     * @type {module:theme-manager}
     */
    themeManager = null

    /**
     * A web-socket manager. Null if not enabled.
     *
     * @type {?module:web-socket-manager}
     */
    webSocketManager = null

    /**
     * A number util.
     *
     * @type {module:num-util}
     */
    numberUtil = null

    /**
     * A page-title util.
     *
     * @type {module:page-title}
     */
    pageTitle = null

    /**
     * A broadcast channel.
     *
     * @type {?module:broadcast-channel}
     */
    broadcastChannel = null

    /**
     * A base path.
     *
     * @type {string}
     */
    basePath = ''

    /**
     * Application parameters.
     *
     * @type {Object}
     */
    appParams = null

    /**
     * @private
     */
    _registerHandlebarsHelpers() {
        Handlebars.registerHelper('img', img => {
            return new Handlebars.SafeString(`<img src="img/${img}" alt="img">`);
        });

        Handlebars.registerHelper('prop', (object, name) => {
            if (name in object) {
                return object[name];
            }
        });

        Handlebars.registerHelper('var', (name, context, options) => {
            if (typeof context === 'undefined') {
                return null;
            }

            let contents = context[name];

            if (options.hash.trim) {
                contents = contents.trim();
            }

            return new Handlebars.SafeString(contents);
        });

        Handlebars.registerHelper('concat', function (left, right) {
            return left + right;
        });

        Handlebars.registerHelper('ifEqual', function (left, right, options) {
            // noinspection EqualityComparisonWithCoercionJS
            if (left == right) {
                return options.fn(this);
            }

            return options.inverse(this);
        });

        Handlebars.registerHelper('ifNotEqual', function (left, right, options) {
            // noinspection EqualityComparisonWithCoercionJS
            if (left != right) {
                return options.fn(this);
            }

            return options.inverse(this);
        });

        Handlebars.registerHelper('ifPropEquals', function (object, property, value, options) {
            // noinspection EqualityComparisonWithCoercionJS
            if (object[property] == value) {
                return options.fn(this);
            }

            return options.inverse(this);
        });

        Handlebars.registerHelper('ifAttrEquals', function (model, attr, value, options) {
            // noinspection EqualityComparisonWithCoercionJS
            if (model.get(attr) == value) {
                return options.fn(this);
            }

            return options.inverse(this);
        });

        Handlebars.registerHelper('ifAttrNotEmpty', function (model, attr, options) {
            const value = model.get(attr);

            if (value !== null && typeof value !== 'undefined') {
                return options.fn(this);
            }

            return options.inverse(this);
        });

        Handlebars.registerHelper('ifNotEmptyHtml', function (value, options) {
            value = value.replace(/\s/g, '');

            if (value) {
                return options.fn(this);
            }

            return options.inverse(this);
        });

        Handlebars.registerHelper('get', (model, name) => model.get(name));

        Handlebars.registerHelper('length', arr => arr.length);

        Handlebars.registerHelper('translate', (name, options) => {
            const scope = options.hash.scope || null;
            const category = options.hash.category || null;

            if (name === 'null') {
                return '';
            }

            return this.language.translate(name, category, scope);
        });

        Handlebars.registerHelper('dropdownItem', (name, options) => {
            const scope = options.hash.scope || null;
            const label = options.hash.label;
            const labelTranslation = options.hash.labelTranslation;
            const data = options.hash.data;
            const hidden = options.hash.hidden;
            const disabled = options.hash.disabled;
            const title = options.hash.title;
            const link = options.hash.link;
            const action = options.hash.action || name;
            const iconHtml = options.hash.iconHtml;
            const iconClass = options.hash.iconClass;

            let html =
                options.hash.html ||
                options.hash.text ||
                (
                    labelTranslation ?
                        this.language.translatePath(labelTranslation) :
                        this.language.translate(label, 'labels', scope)
                );

            if (!options.hash.html) {
                html = this.escapeString(html);
            }

            if (iconHtml) {
                html = iconHtml + ' ' + html;
            }
            else if (iconClass) {
                const iconHtml = $('<span>').addClass(iconClass).get(0).outerHTML;

                html = iconHtml + ' ' + html;
            }

            const $li = $('<li>')
                .addClass(hidden ? 'hidden' : '')
                .addClass(disabled ? 'disabled' : '');

            const $a = $('<a>')
                .attr('role', 'button')
                .attr('tabindex', '0')
                .attr('data-name', name)
                .addClass(options.hash.className || '')
                .addClass('action')
                .html(html);

            if (action) {
                $a.attr('data-action', action);
            }

            $li.append($a);

            link ?
                $a.attr('href', link) :
                $a.attr('role', 'button');

            if (data) {
                for (const key in data) {
                    $a.attr('data-' + Espo.Utils.camelCaseToHyphen(key), data[key]);
                }
            }

            if (disabled) {
                $li.attr('disabled', 'disabled');
            }

            if (title) {
                $a.attr('title', title);
            }

            return new Handlebars.SafeString($li.get(0).outerHTML);
        });

        Handlebars.registerHelper('button', (name, options) => {
            const style = options.hash.style || 'default';
            const scope = options.hash.scope || null;
            const label = options.hash.label || name;
            const labelTranslation = options.hash.labelTranslation;
            const link = options.hash.link;
            const iconHtml = options.hash.iconHtml;
            const iconClass = options.hash.iconClass;

            let html =
                options.hash.html ||
                options.hash.text ||
                (
                    labelTranslation ?
                        this.language.translatePath(labelTranslation) :
                        this.language.translate(label, 'labels', scope)
                );

            if (!options.hash.html) {
                html = this.escapeString(html);
            }

            if (iconHtml) {
                html = iconHtml + ' ' + html;
            }
            else if (iconClass) {
                const iconHtml = $('<span>').addClass(iconClass).get(0).outerHTML;

                html = iconHtml + ' ' + html;
            }

            const tag = link ? '<a>' : '<button>';

            const $button = $(tag)
                .addClass('btn action')
                .addClass(options.hash.className || '')
                .addClass(options.hash.hidden ? 'hidden' : '')
                .addClass(options.hash.disabled ? 'disabled' : '')
                .attr('data-action', name)
                .attr('data-name', name)
                .addClass('btn-' + style)
                .html(html);

            link ?
                $button.href(link) :
                $button.attr('type', 'button')

            if (options.hash.disabled) {
                $button.attr('disabled', 'disabled');
            }

            if (options.hash.title) {
                $button.attr('title', options.hash.title);
            }

            return new Handlebars.SafeString($button.get(0).outerHTML);
        });

        Handlebars.registerHelper('hyphen', (string) => {
            return Espo.Utils.convert(string, 'c-h');
        });

        Handlebars.registerHelper('toDom', (string) => {
            return Espo.Utils.toDom(string);
        });

        // noinspection SpellCheckingInspection
        Handlebars.registerHelper('breaklines', (text) => {
            text = Handlebars.Utils.escapeExpression(text || '');
            text = text.replace(/(\r\n|\n|\r)/gm, '<br>');

            return new Handlebars.SafeString(text);
        });

        Handlebars.registerHelper('complexText', (text, options) => {
            return this.transformMarkdownText(text, options.hash);
        });

        Handlebars.registerHelper('translateOption', (name, options) => {
            const scope = options.hash.scope || null;
            const field = options.hash.field || null;

            if (!field) {
                return '';
            }

            let translationHash = options.hash.translatedOptions || null;

            if (translationHash === null) {
                translationHash = this.language.translate(/** @type {string} */field, 'options', scope) || {};

                if (typeof translationHash !== 'object') {
                    translationHash = {};
                }
            }

            if (name === null) {
                name = '';
            }

            return translationHash[name] || name;
        });

        Handlebars.registerHelper('options', (list, value, options) => {
            if (typeof value === 'undefined') {
                value = false;
            }

            list = list || [];

            let html = '';

            const multiple = (Object.prototype.toString.call(value) === '[object Array]');

            const checkOption = name => {
                if (multiple) {
                    return value.indexOf(name) !== -1;
                }

                return value === name || !value && !name;
            };

            options.hash = /** @type {Object.<string, *>} */ options.hash || {};

            const scope = options.hash.scope || false;
            const category = options.hash.category || false;
            const field = options.hash.field || false;
            const styleMap = options.hash.styleMap || {};

            if (!multiple && options.hash.includeMissingOption && (value || value === '')) {
                if (!~list.indexOf(value)) {
                    list = Espo.Utils.clone(list);

                    list.push(value);
                }
            }

            let translationHash = options.hash.translationHash ||
                options.hash.translatedOptions ||
                null;

            if (translationHash === null) {
                if (!category && field) {
                    translationHash = this.language
                        .translate(/** @type {string}*/field, 'options', /** @type {string}*/scope) || {};

                    if (typeof translationHash !== 'object') {
                        translationHash = {};
                    }
                }
                else {
                    translationHash = {};
                }
            }

            const translate = name => {
                if (!category) {
                    return translationHash[name] || name;
                }

                return this.language.translate(name, category, /** @type {string} */scope);
            };

            for (const key in list) {
                const value = list[key];
                const label = translate(value);

                const $option =
                    $('<option>')
                        .attr('value', value)
                        .addClass(styleMap[value] ? 'text-' + styleMap[value] : '')
                        .text(label);

                if (checkOption(list[key])) {
                    $option.attr('selected', 'selected')
                }

                html += $option.get(0).outerHTML;
            }

            return new Handlebars.SafeString(html);
        });

        Handlebars.registerHelper('basePath', () => {
            return this.basePath || '';
        });
    }

    /**
     * Get an application parameter.
     *
     * @param {string} name
     * @returns {*}
     */
    getAppParam(name) {
        return (this.appParams || {})[name];
    }

    /**
     * Escape a string.
     *
     * @param {string} text A string.
     * @returns {string}
     */
    escapeString(text) {
        return Handlebars.Utils.escapeExpression(text);
    }

    /**
     * Get a user avatar HTML.
     *
     * @param {string} id A user ID.
     * @param {'small'|'medium'|'large'} [size='small'] A size.
     * @param {int} [width=16]
     * @param {string} [additionalClassName]  An additional class-name.
     * @returns {string}
     */
    getAvatarHtml(id, size, width, additionalClassName) {
        if (this.config.get('avatarsDisabled')) {
            return '';
        }

        const t = this.cache ? this.cache.get('app', 'timestamp') : Date.now();

        const basePath = this.basePath || '';
        size = size || 'small';
        width = width || 16;

        let className = 'avatar';

        if (additionalClassName) {
            className += ' ' + additionalClassName;
        }

        // noinspection RequiredAttributes,HtmlRequiredAltAttribute
        return $(`<img>`)
            .attr('src', `${basePath}?entryPoint=avatar&size=${size}&id=${id}&t=${t}`)
            .attr('alt', 'avatar')
            .addClass(className)
            .attr('width', width.toString())
            .get(0).outerHTML;
    }

    /**
     * A Markdown text to HTML (one-line).
     *
     * @param {string} text A text.
     * @returns {Handlebars.SafeString} HTML.
     */
    transformMarkdownInlineText(text) {
        return this.transformMarkdownText(text, {inline: true});
    }

    /**
     * A Markdown text to HTML.
     *
     * @param {string} text A text.
     * @param {{inline?: boolean, linksInNewTab?: boolean}} [options] Options.
     * @returns {Handlebars.SafeString} HTML.
     */
    transformMarkdownText(text, options) {
        text = text || '';

        text = Handlebars.Utils.escapeExpression(text).replace(/&gt;+/g, '>');

        this.mdBeforeList.forEach(item => {
            text = text.replace(item.regex, item.value);
        });

        options = options || {};

        if (options.inline) {
            text = marked.parseInline(text);
        }
        else {
            text = marked.parse(text);
        }

        text = DOMPurify.sanitize(text, {}).toString();

        if (options.linksInNewTab) {
            text = text.replace(/<a href=/gm, '<a target="_blank" rel="noopener noreferrer" href=');
        }

        text = text.replace(
            /<a href="mailto:(.*)"/gm,
            '<a role="button" class="selectable" data-email-address="$1" data-action="mailTo"'
        );

        return new Handlebars.SafeString(text);
    }

    /**
     * Get a color-icon HTML for a scope.
     *
     * @param {string} scope A scope.
     * @param {boolean} [noWhiteSpace=false] No white space.
     * @param {string} [additionalClassName] An additional class-name.
     * @returns {string}
     */
    getScopeColorIconHtml(scope, noWhiteSpace, additionalClassName) {
        if (this.config.get('scopeColorsDisabled') || this.preferences.get('scopeColorsDisabled')) {
            return '';
        }

        const color = this.metadata.get(['clientDefs', scope, 'color']);

        let html = '';

        if (color) {
            const $span = $('<span class="color-icon fas fa-square">');

            $span.css('color', color);

            if (additionalClassName) {
                $span.addClass(additionalClassName);
            }

            html = $span.get(0).outerHTML;
        }

        if (!noWhiteSpace) {
            if (html) {
                html += `<span style="user-select: none;">&nbsp;</span>`;
            }
        }

        return html;
    }

    /**
     * Sanitize HTML.
     *
     * @param {string} text HTML.
     * @param {Object} [options] Options.
     * @returns {string}
     */
    sanitizeHtml(text, options) {
        return DOMPurify.sanitize(text, options);
    }

    /**
     * Moderately sanitize HTML.
     *
     * @param {string} value HTML.
     * @returns {string}
     */
    moderateSanitizeHtml(value) {
        value = value || '';
        value = value.replace(/<\/?(base)[^><]*>/gi, '');
        value = value.replace(/<\/?(object)[^><]*>/gi, '');
        value = value.replace(/<\/?(embed)[^><]*>/gi, '');
        value = value.replace(/<\/?(applet)[^><]*>/gi, '');
        value = value.replace(/<\/?(iframe)[^><]*>/gi, '');
        value = value.replace(/<\/?(script)[^><]*>/gi, '');
        value = value.replace(/<[^><]*([^a-z]on[a-z]+)=[^><]*>/gi, function (match) {
            return match.replace(/[^a-z]on[a-z]+=/gi, ' data-handler-stripped=');
        });

        value = this.stripEventHandlersInHtml(value);

        value = value.replace(/href=" *javascript:(.*?)"/gi, () => {
            return 'removed=""';
        });

        value = value.replace(/href=' *javascript:(.*?)'/gi, () => {
            return 'removed=""';
        });

        value = value.replace(/src=" *javascript:(.*?)"/gi, () => {
            return 'removed=""';
        });

        value = value.replace(/src=' *javascript:(.*?)'/gi, () => {
            return 'removed=""';
        });

        return value;
    }

    /**
     * Strip event handlers in HTML.
     *
     * @param {string} html HTML.
     * @returns {string}
     */
    stripEventHandlersInHtml(html) {
        let j; // @todo Revise.

        function stripHTML() {
            html = html.slice(0, strip) + html.slice(j);
            j = strip;

            strip = false;
        }

        function isValidTagChar(str) {
            return str.match(/[a-z?\\\/!]/i);
        }

        let strip = false;
        let lastQuote = false;

        for (let i = 0; i < html.length; i++){
            if (html[i] === '<' && html[i + 1] && isValidTagChar(html[i + 1])) {
                i++;

                for (let j = i; j < html.length; j++){
                    if (!lastQuote && html[j] === '>'){
                        if (strip) {
                            stripHTML();
                        }

                        i = j;

                        break;
                    }

                    // noinspection JSIncompatibleTypesComparison
                    if (lastQuote === html[j]){
                        lastQuote = false;

                        continue;
                    }

                    if (!lastQuote && html[j - 1] === "=" && (html[j] === "'" || html[j] === '"')) {
                        lastQuote = html[j];
                    }

                    if (!lastQuote && html[j - 2] === " " && html[j - 1] === "o" && html[j] === "n") {
                        strip = j - 2;
                    }

                    if (strip && html[j] === " " && !lastQuote){
                        stripHTML();
                    }
                }
            }
        }

        return html;
    }

    /**
     * Calculate a content container height.
     *
     * @param {JQuery} $el Element.
     * @returns {number}
     */
    calculateContentContainerHeight($el) {
        const smallScreenWidth = this.themeManager.getParam('screenWidthXs');

        const $window = $(window);

        const footerHeight = $('#footer').height() || 26;
        let top = 0;
        const element = $el.get(0);

        if (element) {
            top = element.getBoundingClientRect().top;

            if ($window.width() < smallScreenWidth) {
                const $navbarCollapse = $('#navbar .navbar-body');

                if ($navbarCollapse.hasClass('in') || $navbarCollapse.hasClass('collapsing')) {
                    top -= $navbarCollapse.height();
                }
            }
        }

        const spaceHeight = top + footerHeight;

        return $window.height() - spaceHeight - 20;
    }

    /**
     * Process view-setup-handlers.
     *
     * @param {module:view} view A view.
     * @param {string} type A view-setup-handler type.
     * @param {string} [scope] A scope.
     * @return Promise
     */
    processSetupHandlers(view, type, scope) {
        // noinspection JSUnresolvedReference
        scope = scope || view.scope || view.entityType;

        let handlerIdList = this.metadata.get(['clientDefs', 'Global', 'viewSetupHandlers', type]) || [];

        if (scope) {
            handlerIdList = handlerIdList
                .concat(
                    this.metadata.get(['clientDefs', scope, 'viewSetupHandlers', type]) || []
                );
        }

        if (handlerIdList.length === 0) {
            return Promise.resolve();
        }

        /**
         * @interface
         * @name ViewHelper~Handler
         */

        /**
         * @function
         * @name ViewHelper~Handler#process
         * @param {module:view} [view] Deprecated.
         */
        const promiseList = [];

        for (const id of handlerIdList) {
            const promise = new Promise(resolve => {
                Espo.loader.require(id, /** typeof ViewHelper~Handler */Handler => {
                    const result = (new Handler(view)).process(view);

                    if (result && Object.prototype.toString.call(result) === '[object Promise]') {
                        result.then(() => resolve());

                        return;
                    }

                    resolve();
                });
            });

            promiseList.push(promise);
        }

        return Promise.all(promiseList);
    }

    /** @private */
    _isXsScreen

    /**
     * Is xs screen width.
     *
     * @return {boolean}
     */
    isXsScreen() {
        if (this._isXsScreen == null) {
            this._isXsScreen = window.innerWidth < this.themeManager.getParam('screenWidthXs');
        }

        return this._isXsScreen;
    }
}

export default ViewHelper;
PK]̼أOOacl-portal-manager.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

/** @module acl-portal-manager */

import AclManager from 'acl-manager';
import AclPortal from 'acl-portal';

/**
 * An access checking class for a specific scope for portals.
 */
class AclPortalManager extends AclManager {

    // noinspection JSUnusedGlobalSymbols
    /**
     * Check if a user in an account of a model.
     *
     * @param {module:model} model A model.
     * @returns {boolean|null} True if in an account, null if not clear.
     */
    checkInAccount(model) {
        const impl =
            /** @type {module:acl-portal} */
            this.getImplementation(model.entityType);

        return impl.checkInAccount(model);
    }

    // noinspection JSUnusedGlobalSymbols
    /**
     * Check if a user is a contact-owner to a model.
     *
     * @param {module:model} model A model.
     * @returns {boolean|null} True if in a contact-owner, null if not clear.
     */
    checkIsOwnContact(model) {
        const impl =
            /** @type {module:acl-portal} */
            this.getImplementation(model.entityType);

        return impl.checkIsOwnContact(model);
    }

    /**
     * @param {string} scope A scope.
     * @returns {module:acl-portal}
     */
    getImplementation(scope) {
        if (!(scope in this.implementationHash)) {
            let implementationClass = AclPortal;

            if (scope in this.implementationClassMap) {
                implementationClass = this.implementationClassMap[scope];
            }

            this.implementationHash[scope] =
                new implementationClass(this.getUser(), scope, this.aclAllowDeleteCreated);
        }

        return this.implementationHash[scope];
    }
}

export default AclPortalManager;
PK]�_�3��
acl/import.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import Acl from 'acl';

class ImportAcl extends Acl {

    checkScope(data, action, precise, entityAccessData) {
        return !!data;
    }

    // noinspection JSUnusedGlobalSymbols,JSUnusedLocalSymbols
    checkModelRead(model, data, precise) {
        return true;
    }

    checkIsOwner(model) {
        if (this.getUser().id === model.get('createdById')) {
            return true;
        }

        return false;
    }

    checkModelDelete(model, data, precise) {
        return true;
    }
}

export default ImportAcl;
PK]\ގ(GGacl/user.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import Acl from 'acl';

class UserAcl extends Acl {

    // noinspection JSUnusedGlobalSymbols
    checkModelRead(model, data, precise) {
        if (model.isPortal()) {
            if (this.get('portalPermission') === 'yes') {
                return true;
            }
        }

        return this.checkModel(model, data, 'read', precise);
    }

    checkIsOwner(model) {
        return this.getUser().id === model.id;
    }
}

export default UserAcl;
PK]i�tR�
�
acl/email.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import Acl from 'acl';

class EmailAcl extends Acl {

    // noinspection JSUnusedGlobalSymbols
    checkModelRead(model, data, precise) {
        let result = this.checkModel(model, data, 'read', precise);

        if (result) {
            return true;
        }

        if (data === false) {
            return false;
        }

        let d = data || {};

        if (d.read === 'no') {
            return false;
        }

        if (model.has('usersIds')) {
            if (~(model.get('usersIds') || []).indexOf(this.getUser().id)) {
                return true;
            }
        }
        else if (precise) {
            return null;
        }

        return result;
    }

    checkIsOwner(model) {
        if (
            this.getUser().id === model.get('assignedUserId') ||
            this.getUser().id === model.get('createdById')
        ) {
            return true;
        }

        if (!model.has('assignedUsersIds')) {
            return null;
        }

        if (~(model.get('assignedUsersIds') || []).indexOf(this.getUser().id)) {
            return true;
        }

        return false;
    }

    // noinspection JSUnusedGlobalSymbols
    checkModelEdit(model, data, precise) {
        if (
            model.get('status') === 'Draft' &&
            model.get('createdById') === this.getUser().id
        ) {
            return true;
        }

        return this.checkModel(model, data, 'edit', precise);
    }

    checkModelDelete(model, data, precise) {
        let result = this.checkModel(model, data, 'delete', precise);

        if (result) {
            return true;
        }

        if (data === false) {
            return false;
        }

        let d = data || {};

        if (d.read === 'no') {
            return false;
        }

        if (model.get('createdById') === this.getUser().id) {
            if (model.get('status') !== 'Sent' && model.get('status') !== 'Archived') {
                return true;
            }
        }

        return result;
    }
}

export default EmailAcl;
PK]'�jjacl/notification.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import Acl from 'acl';

class NotificationAcl extends Acl {

    checkIsOwner(model) {
        if (this.getUser().id === model.get('userId')) {
            return true;
        }

        return false;
    }
}

export default NotificationAcl;
PK]�S��]]acl/preferences.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import Acl from 'acl';

class PreferencesAcl extends Acl {

    checkIsOwner(model) {
        if (this.getUser().id === model.id) {
            return true;
        }

        return false;
    }
}

export default PreferencesAcl;
PK],��!��acl/foreign.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import Acl from 'acl';

/**
 * To be used for entities for which access is determined by access to a foreign record.
 */
class ForeignAcl extends Acl {

    checkIsOwner(model) {
        return true;
    }

    checkInTeam(model) {
        return true;
    }
}

export default ForeignAcl;
PK]V��^^acl/team.jsnu�[���/************************************************************************
 * This file is part of EspoCRM.
 *
 * EspoCRM - Open Source CRM application.
 * Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
 * Website: https://www.espocrm.com
 *
 * EspoCRM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * EspoCRM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
 ************************************************************************/

import Acl from 'acl';

class TeamAcl extends Acl {

    checkInTeam(model) {
        const userTeamIdList = this.getUser().getTeamIdList();

        return (userTeamIdList.indexOf(model.id) !== -1);
    }
}

export default TeamAcl;
PKD:]�,�մ�
Filter.phpnu�[���<?php declare(strict_types=1);
/*
 * This file is part of phpunit/php-code-coverage.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace SebastianBergmann\CodeCoverage;

use function array_keys;
use function is_file;
use function realpath;
use function str_contains;
use function str_starts_with;
use SebastianBergmann\FileIterator\Facade as FileIteratorFacade;

final class Filter
{
    /**
     * @psalm-var array<string,true>
     */
    private array $files = [];

    /**
     * @psalm-var array<string,bool>
     */
    private array $isFileCache = [];

    /**
     * @deprecated
     */
    public function includeDirectory(string $directory, string $suffix = '.php', string $prefix = ''): void
    {
        foreach ((new FileIteratorFacade)->getFilesAsArray($directory, $suffix, $prefix) as $file) {
            $this->includeFile($file);
        }
    }

    /**
     * @psalm-param list<string> $files
     */
    public function includeFiles(array $filenames): void
    {
        foreach ($filenames as $filename) {
            $this->includeFile($filename);
        }
    }

    public function includeFile(string $filename): void
    {
        $filename = realpath($filename);

        if (!$filename) {
            return;
        }

        $this->files[$filename] = true;
    }

    /**
     * @deprecated
     */
    public function excludeDirectory(string $directory, string $suffix = '.php', string $prefix = ''): void
    {
        foreach ((new FileIteratorFacade)->getFilesAsArray($directory, $suffix, $prefix) as $file) {
            $this->excludeFile($file);
        }
    }

    /**
     * @deprecated
     */
    public function excludeFile(string $filename): void
    {
        $filename = realpath($filename);

        if (!$filename || !isset($this->files[$filename])) {
            return;
        }

        unset($this->files[$filename]);
    }

    public function isFile(string $filename): bool
    {
        if (isset($this->isFileCache[$filename])) {
            return $this->isFileCache[$filename];
        }

        if ($filename === '-' ||
            str_starts_with($filename, 'vfs://') ||
            str_contains($filename, 'xdebug://debug-eval') ||
            str_contains($filename, 'eval()\'d code') ||
            str_contains($filename, 'runtime-created function') ||
            str_contains($filename, 'runkit created function') ||
            str_contains($filename, 'assert code') ||
            str_contains($filename, 'regexp code') ||
            str_contains($filename, 'Standard input code')) {
            $isFile = false;
        } else {
            $isFile = is_file($filename);
        }

        $this->isFileCache[$filename] = $isFile;

        return $isFile;
    }

    public function isExcluded(string $filename): bool
    {
        return !isset($this->files[$filename]) || !$this->isFile($filename);
    }

    /**
     * @psalm-return list<string>
     */
    public function files(): array
    {
        return array_keys($this->files);
    }

    public function isEmpty(): bool
    {
        return empty($this->files);
    }
}
PKD:]����Version.phpnu�[���<?php declare(strict_types=1);
/*
 * This file is part of phpunit/php-code-coverage.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace SebastianBergmann\CodeCoverage;

use function dirname;
use SebastianBergmann\Version as VersionId;

final class Version
{
    private static string $version = '';

    public static function id(): string
    {
        if (self::$version === '') {
            self::$version = (new VersionId('10.1.16', dirname(__DIR__)))->asString();
        }

        return self::$version;
    }
}
PKD:]u���aaTestStatus/Unknown.phpnu�[���<?php declare(strict_types=1);
/*
 * This file is part of phpunit/php-code-coverage.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace SebastianBergmann\CodeCoverage\Test\TestStatus;

/**
 * @psalm-immutable
 */
final class Unknown extends TestStatus
{
    /**
     * @psalm-assert-if-true Unknown $this
     */
    public function isUnknown(): bool
    {
        return true;
    }

    public function asString(): string
    {
        return 'unknown';
    }
}
PKD:]	=��\\TestStatus/Failure.phpnu�[���<?php declare(strict_types=1);
/*
 * This file is part of phpunit/php-code-coverage.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace SebastianBergmann\CodeCoverage\Test\TestStatus;

/**
 * @psalm-immutable
 */
final class Failure extends Known
{
    /**
     * @psalm-assert-if-true Failure $this
     */
    public function isFailure(): bool
    {
        return true;
    }

    public function asString(): string
    {
        return 'failure';
    }
}
PKD:]��E�TestStatus/Known.phpnu�[���<?php declare(strict_types=1);
/*
 * This file is part of phpunit/php-code-coverage.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace SebastianBergmann\CodeCoverage\Test\TestStatus;

/**
 * @psalm-immutable
 */
abstract class Known extends TestStatus
{
    /**
     * @psalm-assert-if-true Known $this
     */
    public function isKnown(): bool
    {
        return true;
    }
}
PKE:]�0��TestStatus/TestStatus.phpnu�[���<?php declare(strict_types=1);
/*
 * This file is part of phpunit/php-code-coverage.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace SebastianBergmann\CodeCoverage\Test\TestStatus;

/**
 * @psalm-immutable
 */
abstract class TestStatus
{
    public static function unknown(): self
    {
        return new Unknown;
    }

    public static function success(): self
    {
        return new Success;
    }

    public static function failure(): self
    {
        return new Failure;
    }

    /**
     * @psalm-assert-if-true Known $this
     */
    public function isKnown(): bool
    {
        return false;
    }

    /**
     * @psalm-assert-if-true Unknown $this
     */
    public function isUnknown(): bool
    {
        return false;
    }

    /**
     * @psalm-assert-if-true Success $this
     */
    public function isSuccess(): bool
    {
        return false;
    }

    /**
     * @psalm-assert-if-true Failure $this
     */
    public function isFailure(): bool
    {
        return false;
    }

    abstract public function asString(): string;
}
PKE:]��i\\TestStatus/Success.phpnu�[���<?php declare(strict_types=1);
/*
 * This file is part of phpunit/php-code-coverage.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace SebastianBergmann\CodeCoverage\Test\TestStatus;

/**
 * @psalm-immutable
 */
final class Success extends Known
{
    /**
     * @psalm-assert-if-true Success $this
     */
    public function isSuccess(): bool
    {
        return true;
    }

    public function asString(): string
    {
        return 'success';
    }
}
PKE:]�~Y_�E�ECodeCoverage.phpnu�[���<?php declare(strict_types=1);
/*
 * This file is part of phpunit/php-code-coverage.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace SebastianBergmann\CodeCoverage;

use function array_diff;
use function array_diff_key;
use function array_flip;
use function array_keys;
use function array_merge;
use function array_merge_recursive;
use function array_unique;
use function count;
use function explode;
use function is_array;
use function is_file;
use function sort;
use ReflectionClass;
use SebastianBergmann\CodeCoverage\Data\ProcessedCodeCoverageData;
use SebastianBergmann\CodeCoverage\Data\RawCodeCoverageData;
use SebastianBergmann\CodeCoverage\Driver\Driver;
use SebastianBergmann\CodeCoverage\Node\Builder;
use SebastianBergmann\CodeCoverage\Node\Directory;
use SebastianBergmann\CodeCoverage\StaticAnalysis\CachingFileAnalyser;
use SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser;
use SebastianBergmann\CodeCoverage\StaticAnalysis\ParsingFileAnalyser;
use SebastianBergmann\CodeCoverage\Test\TestSize\TestSize;
use SebastianBergmann\CodeCoverage\Test\TestStatus\TestStatus;
use SebastianBergmann\CodeUnitReverseLookup\Wizard;

/**
 * Provides collection functionality for PHP code coverage information.
 *
 * @psalm-type TestType = array{
 *     size: string,
 *     status: string,
 * }
 */
final class CodeCoverage
{
    private const UNCOVERED_FILES = 'UNCOVERED_FILES';
    private readonly Driver $driver;
    private readonly Filter $filter;
    private readonly Wizard $wizard;
    private bool $checkForUnintentionallyCoveredCode = false;
    private bool $includeUncoveredFiles              = true;
    private bool $ignoreDeprecatedCode               = false;
    private ?string $currentId                       = null;
    private ?TestSize $currentSize                   = null;
    private ProcessedCodeCoverageData $data;
    private bool $useAnnotationsForIgnoringCode = true;

    /**
     * @psalm-var array<string,list<int>>
     */
    private array $linesToBeIgnored = [];

    /**
     * @psalm-var array<string, TestType>
     */
    private array $tests = [];

    /**
     * @psalm-var list<class-string>
     */
    private array $parentClassesExcludedFromUnintentionallyCoveredCodeCheck = [];
    private ?FileAnalyser $analyser                                         = null;
    private ?string $cacheDirectory                                         = null;
    private ?Directory $cachedReport                                        = null;

    public function __construct(Driver $driver, Filter $filter)
    {
        $this->driver = $driver;
        $this->filter = $filter;
        $this->data   = new ProcessedCodeCoverageData;
        $this->wizard = new Wizard;
    }

    /**
     * Returns the code coverage information as a graph of node objects.
     */
    public function getReport(): Directory
    {
        if ($this->cachedReport === null) {
            $this->cachedReport = (new Builder($this->analyser()))->build($this);
        }

        return $this->cachedReport;
    }

    /**
     * Clears collected code coverage data.
     */
    public function clear(): void
    {
        $this->currentId    = null;
        $this->currentSize  = null;
        $this->data         = new ProcessedCodeCoverageData;
        $this->tests        = [];
        $this->cachedReport = null;
    }

    /**
     * @internal
     */
    public function clearCache(): void
    {
        $this->cachedReport = null;
    }

    /**
     * Returns the filter object used.
     */
    public function filter(): Filter
    {
        return $this->filter;
    }

    /**
     * Returns the collected code coverage data.
     */
    public function getData(bool $raw = false): ProcessedCodeCoverageData
    {
        if (!$raw) {
            if ($this->includeUncoveredFiles) {
                $this->addUncoveredFilesFromFilter();
            }
        }

        return $this->data;
    }

    /**
     * Sets the coverage data.
     */
    public function setData(ProcessedCodeCoverageData $data): void
    {
        $this->data = $data;
    }

    /**
     * @psalm-return array<string, TestType>
     */
    public function getTests(): array
    {
        return $this->tests;
    }

    /**
     * @psalm-param array<string, TestType> $tests
     */
    public function setTests(array $tests): void
    {
        $this->tests = $tests;
    }

    public function start(string $id, ?TestSize $size = null, bool $clear = false): void
    {
        if ($clear) {
            $this->clear();
        }

        $this->currentId   = $id;
        $this->currentSize = $size;

        $this->driver->start();

        $this->cachedReport = null;
    }

    /**
     * @psalm-param array<string,list<int>> $linesToBeIgnored
     */
    public function stop(bool $append = true, ?TestStatus $status = null, array|false $linesToBeCovered = [], array $linesToBeUsed = [], array $linesToBeIgnored = []): RawCodeCoverageData
    {
        $data = $this->driver->stop();

        $this->linesToBeIgnored = array_merge_recursive(
            $this->linesToBeIgnored,
            $linesToBeIgnored,
        );

        $this->append($data, null, $append, $status, $linesToBeCovered, $linesToBeUsed, $linesToBeIgnored);

        $this->currentId    = null;
        $this->currentSize  = null;
        $this->cachedReport = null;

        return $data;
    }

    /**
     * @psalm-param array<string,list<int>> $linesToBeIgnored
     *
     * @throws ReflectionException
     * @throws TestIdMissingException
     * @throws UnintentionallyCoveredCodeException
     */
    public function append(RawCodeCoverageData $rawData, ?string $id = null, bool $append = true, ?TestStatus $status = null, array|false $linesToBeCovered = [], array $linesToBeUsed = [], array $linesToBeIgnored = []): void
    {
        if ($id === null) {
            $id = $this->currentId;
        }

        if ($id === null) {
            throw new TestIdMissingException;
        }

        $this->cachedReport = null;

        if ($status === null) {
            $status = TestStatus::unknown();
        }

        $size = $this->currentSize;

        if ($size === null) {
            $size = TestSize::unknown();
        }

        $this->applyFilter($rawData);

        $this->applyExecutableLinesFilter($rawData);

        if ($this->useAnnotationsForIgnoringCode) {
            $this->applyIgnoredLinesFilter($rawData, $linesToBeIgnored);
        }

        $this->data->initializeUnseenData($rawData);

        if (!$append) {
            return;
        }

        if ($id === self::UNCOVERED_FILES) {
            return;
        }

        $this->applyCoversAndUsesFilter(
            $rawData,
            $linesToBeCovered,
            $linesToBeUsed,
            $size,
        );

        if (empty($rawData->lineCoverage())) {
            return;
        }

        $this->tests[$id] = [
            'size'   => $size->asString(),
            'status' => $status->asString(),
        ];

        $this->data->markCodeAsExecutedByTestCase($id, $rawData);
    }

    /**
     * Merges the data from another instance.
     */
    public function merge(self $that): void
    {
        $this->filter->includeFiles(
            $that->filter()->files(),
        );

        $this->data->merge($that->data);

        $this->tests = array_merge($this->tests, $that->getTests());

        $this->cachedReport = null;
    }

    public function enableCheckForUnintentionallyCoveredCode(): void
    {
        $this->checkForUnintentionallyCoveredCode = true;
    }

    public function disableCheckForUnintentionallyCoveredCode(): void
    {
        $this->checkForUnintentionallyCoveredCode = false;
    }

    public function includeUncoveredFiles(): void
    {
        $this->includeUncoveredFiles = true;
    }

    public function excludeUncoveredFiles(): void
    {
        $this->includeUncoveredFiles = false;
    }

    public function enableAnnotationsForIgnoringCode(): void
    {
        $this->useAnnotationsForIgnoringCode = true;
    }

    public function disableAnnotationsForIgnoringCode(): void
    {
        $this->useAnnotationsForIgnoringCode = false;
    }

    public function ignoreDeprecatedCode(): void
    {
        $this->ignoreDeprecatedCode = true;
    }

    public function doNotIgnoreDeprecatedCode(): void
    {
        $this->ignoreDeprecatedCode = false;
    }

    /**
     * @psalm-assert-if-true !null $this->cacheDirectory
     */
    public function cachesStaticAnalysis(): bool
    {
        return $this->cacheDirectory !== null;
    }

    public function cacheStaticAnalysis(string $directory): void
    {
        $this->cacheDirectory = $directory;
    }

    public function doNotCacheStaticAnalysis(): void
    {
        $this->cacheDirectory = null;
    }

    /**
     * @throws StaticAnalysisCacheNotConfiguredException
     */
    public function cacheDirectory(): string
    {
        if (!$this->cachesStaticAnalysis()) {
            throw new StaticAnalysisCacheNotConfiguredException(
                'The static analysis cache is not configured',
            );
        }

        return $this->cacheDirectory;
    }

    /**
     * @psalm-param class-string $className
     */
    public function excludeSubclassesOfThisClassFromUnintentionallyCoveredCodeCheck(string $className): void
    {
        $this->parentClassesExcludedFromUnintentionallyCoveredCodeCheck[] = $className;
    }

    public function enableBranchAndPathCoverage(): void
    {
        $this->driver->enableBranchAndPathCoverage();
    }

    public function disableBranchAndPathCoverage(): void
    {
        $this->driver->disableBranchAndPathCoverage();
    }

    public function collectsBranchAndPathCoverage(): bool
    {
        return $this->driver->collectsBranchAndPathCoverage();
    }

    public function detectsDeadCode(): bool
    {
        return $this->driver->detectsDeadCode();
    }

    /**
     * @throws ReflectionException
     * @throws UnintentionallyCoveredCodeException
     */
    private function applyCoversAndUsesFilter(RawCodeCoverageData $rawData, array|false $linesToBeCovered, array $linesToBeUsed, TestSize $size): void
    {
        if ($linesToBeCovered === false) {
            $rawData->clear();

            return;
        }

        if (empty($linesToBeCovered)) {
            return;
        }

        if ($this->checkForUnintentionallyCoveredCode && !$size->isMedium() && !$size->isLarge()) {
            $this->performUnintentionallyCoveredCodeCheck($rawData, $linesToBeCovered, $linesToBeUsed);
        }

        $rawLineData         = $rawData->lineCoverage();
        $filesWithNoCoverage = array_diff_key($rawLineData, $linesToBeCovered);

        foreach (array_keys($filesWithNoCoverage) as $fileWithNoCoverage) {
            $rawData->removeCoverageDataForFile($fileWithNoCoverage);
        }

        if (is_array($linesToBeCovered)) {
            foreach ($linesToBeCovered as $fileToBeCovered => $includedLines) {
                $rawData->keepLineCoverageDataOnlyForLines($fileToBeCovered, $includedLines);
                $rawData->keepFunctionCoverageDataOnlyForLines($fileToBeCovered, $includedLines);
            }
        }
    }

    private function applyFilter(RawCodeCoverageData $data): void
    {
        if ($this->filter->isEmpty()) {
            return;
        }

        foreach (array_keys($data->lineCoverage()) as $filename) {
            if ($this->filter->isExcluded($filename)) {
                $data->removeCoverageDataForFile($filename);
            }
        }
    }

    private function applyExecutableLinesFilter(RawCodeCoverageData $data): void
    {
        foreach (array_keys($data->lineCoverage()) as $filename) {
            if (!$this->filter->isFile($filename)) {
                continue;
            }

            $linesToBranchMap = $this->analyser()->executableLinesIn($filename);

            $data->keepLineCoverageDataOnlyForLines(
                $filename,
                array_keys($linesToBranchMap),
            );

            $data->markExecutableLineByBranch(
                $filename,
                $linesToBranchMap,
            );
        }
    }

    /**
     * @psalm-param array<string,list<int>> $linesToBeIgnored
     */
    private function applyIgnoredLinesFilter(RawCodeCoverageData $data, array $linesToBeIgnored): void
    {
        foreach (array_keys($data->lineCoverage()) as $filename) {
            if (!$this->filter->isFile($filename)) {
                continue;
            }

            if (isset($linesToBeIgnored[$filename])) {
                $data->removeCoverageDataForLines(
                    $filename,
                    $linesToBeIgnored[$filename],
                );
            }

            $data->removeCoverageDataForLines(
                $filename,
                $this->analyser()->ignoredLinesFor($filename),
            );
        }
    }

    /**
     * @throws UnintentionallyCoveredCodeException
     */
    private function addUncoveredFilesFromFilter(): void
    {
        $uncoveredFiles = array_diff(
            $this->filter->files(),
            $this->data->coveredFiles(),
        );

        foreach ($uncoveredFiles as $uncoveredFile) {
            if (is_file($uncoveredFile)) {
                $this->append(
                    RawCodeCoverageData::fromUncoveredFile(
                        $uncoveredFile,
                        $this->analyser(),
                    ),
                    self::UNCOVERED_FILES,
                    linesToBeIgnored: $this->linesToBeIgnored,
                );
            }
        }
    }

    /**
     * @throws ReflectionException
     * @throws UnintentionallyCoveredCodeException
     */
    private function performUnintentionallyCoveredCodeCheck(RawCodeCoverageData $data, array $linesToBeCovered, array $linesToBeUsed): void
    {
        $allowedLines = $this->getAllowedLines(
            $linesToBeCovered,
            $linesToBeUsed,
        );

        $unintentionallyCoveredUnits = [];

        foreach ($data->lineCoverage() as $file => $_data) {
            foreach ($_data as $line => $flag) {
                if ($flag === 1 && !isset($allowedLines[$file][$line])) {
                    $unintentionallyCoveredUnits[] = $this->wizard->lookup($file, $line);
                }
            }
        }

        $unintentionallyCoveredUnits = $this->processUnintentionallyCoveredUnits($unintentionallyCoveredUnits);

        if (!empty($unintentionallyCoveredUnits)) {
            throw new UnintentionallyCoveredCodeException(
                $unintentionallyCoveredUnits,
            );
        }
    }

    private function getAllowedLines(array $linesToBeCovered, array $linesToBeUsed): array
    {
        $allowedLines = [];

        foreach (array_keys($linesToBeCovered) as $file) {
            if (!isset($allowedLines[$file])) {
                $allowedLines[$file] = [];
            }

            $allowedLines[$file] = array_merge(
                $allowedLines[$file],
                $linesToBeCovered[$file],
            );
        }

        foreach (array_keys($linesToBeUsed) as $file) {
            if (!isset($allowedLines[$file])) {
                $allowedLines[$file] = [];
            }

            $allowedLines[$file] = array_merge(
                $allowedLines[$file],
                $linesToBeUsed[$file],
            );
        }

        foreach (array_keys($allowedLines) as $file) {
            $allowedLines[$file] = array_flip(
                array_unique($allowedLines[$file]),
            );
        }

        return $allowedLines;
    }

    /**
     * @param list<string> $unintentionallyCoveredUnits
     *
     * @throws ReflectionException
     *
     * @return list<string>
     */
    private function processUnintentionallyCoveredUnits(array $unintentionallyCoveredUnits): array
    {
        $unintentionallyCoveredUnits = array_unique($unintentionallyCoveredUnits);
        $processed                   = [];

        foreach ($unintentionallyCoveredUnits as $unintentionallyCoveredUnit) {
            $tmp = explode('::', $unintentionallyCoveredUnit);

            if (count($tmp) !== 2) {
                $processed[] = $unintentionallyCoveredUnit;

                continue;
            }

            try {
                $class = new ReflectionClass($tmp[0]);

                foreach ($this->parentClassesExcludedFromUnintentionallyCoveredCodeCheck as $parentClass) {
                    if ($class->isSubclassOf($parentClass)) {
                        continue 2;
                    }
                }
            } catch (\ReflectionException $e) {
                throw new ReflectionException(
                    $e->getMessage(),
                    $e->getCode(),
                    $e,
                );
            }

            $processed[] = $tmp[0];
        }

        $processed = array_unique($processed);

        sort($processed);

        return $processed;
    }

    private function analyser(): FileAnalyser
    {
        if ($this->analyser !== null) {
            return $this->analyser;
        }

        $this->analyser = new ParsingFileAnalyser(
            $this->useAnnotationsForIgnoringCode,
            $this->ignoreDeprecatedCode,
        );

        if ($this->cachesStaticAnalysis()) {
            $this->analyser = new CachingFileAnalyser(
                $this->cacheDirectory,
                $this->analyser,
                $this->useAnnotationsForIgnoringCode,
                $this->ignoreDeprecatedCode,
            );
        }

        return $this->analyser;
    }
}
PKE:]'�q��Report/Html/Colors.phpnu�[���<?php declare(strict_types=1);
/*
 * This file is part of phpunit/php-code-coverage.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace SebastianBergmann\CodeCoverage\Report\Html;

/**
 * @psalm-immutable
 */
final class Colors
{
    private readonly string $successLow;
    private readonly string $successMedium;
    private readonly string $successHigh;
    private readonly string $warning;
    private readonly string $danger;

    public static function default(): self
    {
        return new self('#dff0d8', '#c3e3b5', '#99cb84', '#fcf8e3', '#f2dede');
    }

    public static function from(string $successLow, string $successMedium, string $successHigh, string $warning, string $danger): self
    {
        return new self($successLow, $successMedium, $successHigh, $warning, $danger);
    }

    private function __construct(string $successLow, string $successMedium, string $successHigh, string $warning, string $danger)
    {
        $this->successLow    = $successLow;
        $this->successMedium = $successMedium;
        $this->successHigh   = $successHigh;
        $this->warning       = $warning;
        $this->danger        = $danger;
    }

    public function successLow(): string
    {
        return $this->successLow;
    }

    public function successMedium(): string
    {
        return $this->successMedium;
    }

    public function successHigh(): string
    {
        return $this->successHigh;
    }

    public function warning(): string
    {
        return $this->warning;
    }

    public function danger(): string
    {
        return $this->danger;
    }
}
PKE:]��L�FFReport/Html/CustomCssFile.phpnu�[���<?php declare(strict_types=1);
/*
 * This file is part of phpunit/php-code-coverage.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace SebastianBergmann\CodeCoverage\Report\Html;

use function is_file;
use SebastianBergmann\CodeCoverage\InvalidArgumentException;

/**
 * @psalm-immutable
 */
final class CustomCssFile
{
    private readonly string $path;

    public static function default(): self
    {
        return new self(__DIR__ . '/Renderer/Template/css/custom.css');
    }

    /**
     * @throws InvalidArgumentException
     */
    public static function from(string $path): self
    {
        if (!is_file($path)) {
            throw new InvalidArgumentException(
                '$path does not exist',
            );
        }

        return new self($path);
    }

    private function __construct(string $path)
    {
        $this->path = $path;
    }

    public function path(): string
    {
        return $this->path;
    }
}
PKE:]��$N�'�'Report/Html/Renderer.phpnu�[���<?php declare(strict_types=1);
/*
 * This file is part of phpunit/php-code-coverage.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace SebastianBergmann\CodeCoverage\Report\Html;

use function array_pop;
use function count;
use function sprintf;
use function str_repeat;
use function substr_count;
use SebastianBergmann\CodeCoverage\Node\AbstractNode;
use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode;
use SebastianBergmann\CodeCoverage\Node\File as FileNode;
use SebastianBergmann\CodeCoverage\Report\Thresholds;
use SebastianBergmann\CodeCoverage\Version;
use SebastianBergmann\Environment\Runtime;
use SebastianBergmann\Template\Template;

/**
 * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
 */
abstract class Renderer
{
    protected string $templatePath;
    protected string $generator;
    protected string $date;
    protected Thresholds $thresholds;
    protected bool $hasBranchCoverage;
    protected string $version;

    public function __construct(string $templatePath, string $generator, string $date, Thresholds $thresholds, bool $hasBranchCoverage)
    {
        $this->templatePath      = $templatePath;
        $this->generator         = $generator;
        $this->date              = $date;
        $this->thresholds        = $thresholds;
        $this->version           = Version::id();
        $this->hasBranchCoverage = $hasBranchCoverage;
    }

    protected function renderItemTemplate(Template $template, array $data): string
    {
        $numSeparator = '&nbsp;/&nbsp;';

        if (isset($data['numClasses']) && $data['numClasses'] > 0) {
            $classesLevel = $this->colorLevel($data['testedClassesPercent']);

            $classesNumber = $data['numTestedClasses'] . $numSeparator .
                $data['numClasses'];

            $classesBar = $this->coverageBar(
                $data['testedClassesPercent'],
            );
        } else {
            $classesLevel                         = '';
            $classesNumber                        = '0' . $numSeparator . '0';
            $classesBar                           = '';
            $data['testedClassesPercentAsString'] = 'n/a';
        }

        if ($data['numMethods'] > 0) {
            $methodsLevel = $this->colorLevel($data['testedMethodsPercent']);

            $methodsNumber = $data['numTestedMethods'] . $numSeparator .
                $data['numMethods'];

            $methodsBar = $this->coverageBar(
                $data['testedMethodsPercent'],
            );
        } else {
            $methodsLevel                         = '';
            $methodsNumber                        = '0' . $numSeparator . '0';
            $methodsBar                           = '';
            $data['testedMethodsPercentAsString'] = 'n/a';
        }

        if ($data['numExecutableLines'] > 0) {
            $linesLevel = $this->colorLevel($data['linesExecutedPercent']);

            $linesNumber = $data['numExecutedLines'] . $numSeparator .
                $data['numExecutableLines'];

            $linesBar = $this->coverageBar(
                $data['linesExecutedPercent'],
            );
        } else {
            $linesLevel                           = '';
            $linesNumber                          = '0' . $numSeparator . '0';
            $linesBar                             = '';
            $data['linesExecutedPercentAsString'] = 'n/a';
        }

        if ($data['numExecutablePaths'] > 0) {
            $pathsLevel = $this->colorLevel($data['pathsExecutedPercent']);

            $pathsNumber = $data['numExecutedPaths'] . $numSeparator .
                $data['numExecutablePaths'];

            $pathsBar = $this->coverageBar(
                $data['pathsExecutedPercent'],
            );
        } else {
            $pathsLevel                           = '';
            $pathsNumber                          = '0' . $numSeparator . '0';
            $pathsBar                             = '';
            $data['pathsExecutedPercentAsString'] = 'n/a';
        }

        if ($data['numExecutableBranches'] > 0) {
            $branchesLevel = $this->colorLevel($data['branchesExecutedPercent']);

            $branchesNumber = $data['numExecutedBranches'] . $numSeparator .
                $data['numExecutableBranches'];

            $branchesBar = $this->coverageBar(
                $data['branchesExecutedPercent'],
            );
        } else {
            $branchesLevel                           = '';
            $branchesNumber                          = '0' . $numSeparator . '0';
            $branchesBar                             = '';
            $data['branchesExecutedPercentAsString'] = 'n/a';
        }

        $template->setVar(
            [
                'icon'                      => $data['icon'] ?? '',
                'crap'                      => $data['crap'] ?? '',
                'name'                      => $data['name'],
                'lines_bar'                 => $linesBar,
                'lines_executed_percent'    => $data['linesExecutedPercentAsString'],
                'lines_level'               => $linesLevel,
                'lines_number'              => $linesNumber,
                'paths_bar'                 => $pathsBar,
                'paths_executed_percent'    => $data['pathsExecutedPercentAsString'],
                'paths_level'               => $pathsLevel,
                'paths_number'              => $pathsNumber,
                'branches_bar'              => $branchesBar,
                'branches_executed_percent' => $data['branchesExecutedPercentAsString'],
                'branches_level'            => $branchesLevel,
                'branches_number'           => $branchesNumber,
                'methods_bar'               => $methodsBar,
                'methods_tested_percent'    => $data['testedMethodsPercentAsString'],
                'methods_level'             => $methodsLevel,
                'methods_number'            => $methodsNumber,
                'classes_bar'               => $classesBar,
                'classes_tested_percent'    => $data['testedClassesPercentAsString'] ?? '',
                'classes_level'             => $classesLevel,
                'classes_number'            => $classesNumber,
            ],
        );

        return $template->render();
    }

    protected function setCommonTemplateVariables(Template $template, AbstractNode $node): void
    {
        $template->setVar(
            [
                'id'               => $node->id(),
                'full_path'        => $node->pathAsString(),
                'path_to_root'     => $this->pathToRoot($node),
                'breadcrumbs'      => $this->breadcrumbs($node),
                'date'             => $this->date,
                'version'          => $this->version,
                'runtime'          => $this->runtimeString(),
                'generator'        => $this->generator,
                'low_upper_bound'  => $this->thresholds->lowUpperBound(),
                'high_lower_bound' => $this->thresholds->highLowerBound(),
            ],
        );
    }

    protected function breadcrumbs(AbstractNode $node): string
    {
        $breadcrumbs = '';
        $path        = $node->pathAsArray();
        $pathToRoot  = [];
        $max         = count($path);

        if ($node instanceof FileNode) {
            $max--;
        }

        for ($i = 0; $i < $max; $i++) {
            $pathToRoot[] = str_repeat('../', $i);
        }

        foreach ($path as $step) {
            if ($step !== $node) {
                $breadcrumbs .= $this->inactiveBreadcrumb(
                    $step,
                    array_pop($pathToRoot),
                );
            } else {
                $breadcrumbs .= $this->activeBreadcrumb($step);
            }
        }

        return $breadcrumbs;
    }

    protected function activeBreadcrumb(AbstractNode $node): string
    {
        $buffer = sprintf(
            '         <li class="breadcrumb-item active">%s</li>' . "\n",
            $node->name(),
        );

        if ($node instanceof DirectoryNode) {
            $buffer .= '         <li class="breadcrumb-item">(<a href="dashboard.html">Dashboard</a>)</li>' . "\n";
        }

        return $buffer;
    }

    protected function inactiveBreadcrumb(AbstractNode $node, string $pathToRoot): string
    {
        return sprintf(
            '         <li class="breadcrumb-item"><a href="%sindex.html">%s</a></li>' . "\n",
            $pathToRoot,
            $node->name(),
        );
    }

    protected function pathToRoot(AbstractNode $node): string
    {
        $id    = $node->id();
        $depth = substr_count($id, '/');

        if ($id !== 'index' &&
            $node instanceof DirectoryNode) {
            $depth++;
        }

        return str_repeat('../', $depth);
    }

    protected function coverageBar(float $percent): string
    {
        $level = $this->colorLevel($percent);

        $templateName = $this->templatePath . ($this->hasBranchCoverage ? 'coverage_bar_branch.html' : 'coverage_bar.html');
        $template     = new Template(
            $templateName,
            '{{',
            '}}',
        );

        $template->setVar(['level' => $level, 'percent' => sprintf('%.2F', $percent)]);

        return $template->render();
    }

    protected function colorLevel(float $percent): string
    {
        if ($percent <= $this->thresholds->lowUpperBound()) {
            return 'danger';
        }

        if ($percent > $this->thresholds->lowUpperBound() &&
            $percent < $this->thresholds->highLowerBound()) {
            return 'warning';
        }

        return 'success';
    }

    private function runtimeString(): string
    {
        $runtime = new Runtime;

        return sprintf(
            '<a href="%s" target="_top">%s %s</a>',
            $runtime->getVendorUrl(),
            $runtime->getName(),
            $runtime->getVersion(),
        );
    }
}
PKE:]�-�(Report/Html/Facade.phpnu�[���<?php declare(strict_types=1);
/*
 * This file is part of phpunit/php-code-coverage.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace SebastianBergmann\CodeCoverage\Report\Html;

use const DIRECTORY_SEPARATOR;
use function copy;
use function date;
use function dirname;
use function str_ends_with;
use SebastianBergmann\CodeCoverage\CodeCoverage;
use SebastianBergmann\CodeCoverage\FileCouldNotBeWrittenException;
use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode;
use SebastianBergmann\CodeCoverage\Report\Thresholds;
use SebastianBergmann\CodeCoverage\Util\Filesystem;
use SebastianBergmann\Template\Exception;
use SebastianBergmann\Template\Template;

final class Facade
{
    private readonly string $templatePath;
    private readonly string $generator;
    private readonly Colors $colors;
    private readonly Thresholds $thresholds;
    private readonly CustomCssFile $customCssFile;

    public function __construct(string $generator = '', ?Colors $colors = null, ?Thresholds $thresholds = null, ?CustomCssFile $customCssFile = null)
    {
        $this->generator     = $generator;
        $this->colors        = $colors ?? Colors::default();
        $this->thresholds    = $thresholds ?? Thresholds::default();
        $this->customCssFile = $customCssFile ?? CustomCssFile::default();
        $this->templatePath  = __DIR__ . '/Renderer/Template/';
    }

    public function process(CodeCoverage $coverage, string $target): void
    {
        $target = $this->directory($target);
        $report = $coverage->getReport();
        $date   = date('D M j G:i:s T Y');

        $dashboard = new Dashboard(
            $this->templatePath,
            $this->generator,
            $date,
            $this->thresholds,
            $coverage->collectsBranchAndPathCoverage(),
        );

        $directory = new Directory(
            $this->templatePath,
            $this->generator,
            $date,
            $this->thresholds,
            $coverage->collectsBranchAndPathCoverage(),
        );

        $file = new File(
            $this->templatePath,
            $this->generator,
            $date,
            $this->thresholds,
            $coverage->collectsBranchAndPathCoverage(),
        );

        $directory->render($report, $target . 'index.html');
        $dashboard->render($report, $target . 'dashboard.html');

        foreach ($report as $node) {
            $id = $node->id();

            if ($node instanceof DirectoryNode) {
                Filesystem::createDirectory($target . $id);

                $directory->render($node, $target . $id . '/index.html');
                $dashboard->render($node, $target . $id . '/dashboard.html');
            } else {
                $dir = dirname($target . $id);

                Filesystem::createDirectory($dir);

                $file->render($node, $target . $id);
            }
        }

        $this->copyFiles($target);
        $this->renderCss($target);
    }

    private function copyFiles(string $target): void
    {
        $dir = $this->directory($target . '_css');

        copy($this->templatePath . 'css/bootstrap.min.css', $dir . 'bootstrap.min.css');
        copy($this->templatePath . 'css/nv.d3.min.css', $dir . 'nv.d3.min.css');
        copy($this->customCssFile->path(), $dir . 'custom.css');
        copy($this->templatePath . 'css/octicons.css', $dir . 'octicons.css');

        $dir = $this->directory($target . '_icons');
        copy($this->templatePath . 'icons/file-code.svg', $dir . 'file-code.svg');
        copy($this->templatePath . 'icons/file-directory.svg', $dir . 'file-directory.svg');

        $dir = $this->directory($target . '_js');
        copy($this->templatePath . 'js/bootstrap.min.js', $dir . 'bootstrap.min.js');
        copy($this->templatePath . 'js/popper.min.js', $dir . 'popper.min.js');
        copy($this->templatePath . 'js/d3.min.js', $dir . 'd3.min.js');
        copy($this->templatePath . 'js/jquery.min.js', $dir . 'jquery.min.js');
        copy($this->templatePath . 'js/nv.d3.min.js', $dir . 'nv.d3.min.js');
        copy($this->templatePath . 'js/file.js', $dir . 'file.js');
    }

    private function renderCss(string $target): void
    {
        $template = new Template($this->templatePath . 'css/style.css', '{{', '}}');

        $template->setVar(
            [
                'success-low'    => $this->colors->successLow(),
                'success-medium' => $this->colors->successMedium(),
                'success-high'   => $this->colors->successHigh(),
                'warning'        => $this->colors->warning(),
                'danger'         => $this->colors->danger(),
            ],
        );

        try {
            $template->renderTo($this->directory($target . '_css') . 'style.css');
        } catch (Exception $e) {
            throw new FileCouldNotBeWrittenException(
                $e->getMessage(),
                $e->getCode(),
                $e,
            );
        }
    }

    private function directory(string $directory): string
    {
        if (!str_ends_with($directory, DIRECTORY_SEPARATOR)) {
            $directory .= DIRECTORY_SEPARATOR;
        }

        Filesystem::createDirectory($directory);

        return $directory;
    }
}
PKE:][���'�'"Report/Html/Renderer/Dashboard.phpnu�[���<?php declare(strict_types=1);
/*
 * This file is part of phpunit/php-code-coverage.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace SebastianBergmann\CodeCoverage\Report\Html;

use function array_values;
use function arsort;
use function asort;
use function count;
use function explode;
use function floor;
use function json_encode;
use function sprintf;
use function str_replace;
use SebastianBergmann\CodeCoverage\FileCouldNotBeWrittenException;
use SebastianBergmann\CodeCoverage\Node\AbstractNode;
use SebastianBergmann\CodeCoverage\Node\Directory as DirectoryNode;
use SebastianBergmann\Template\Exception;
use SebastianBergmann\Template\Template;

/**
 * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
 */
final class Dashboard extends Renderer
{
    public function render(DirectoryNode $node, string $file): void
    {
        $classes      = $node->classesAndTraits();
        $templateName = $this->templatePath . ($this->hasBranchCoverage ? 'dashboard_branch.html' : 'dashboard.html');
        $template     = new Template(
            $templateName,
            '{{',
            '}}',
        );

        $this->setCommonTemplateVariables($template, $node);

        $baseLink             = $node->id() . '/';
        $complexity           = $this->complexity($classes, $baseLink);
        $coverageDistribution = $this->coverageDistribution($classes);
        $insufficientCoverage = $this->insufficientCoverage($classes, $baseLink);
        $projectRisks         = $this->projectRisks($classes, $baseLink);

        $template->setVar(
            [
                'insufficient_coverage_classes' => $insufficientCoverage['class'],
                'insufficient_coverage_methods' => $insufficientCoverage['method'],
                'project_risks_classes'         => $projectRisks['class'],
                'project_risks_methods'         => $projectRisks['method'],
                'complexity_class'              => $complexity['class'],
                'complexity_method'             => $complexity['method'],
                'class_coverage_distribution'   => $coverageDistribution['class'],
                'method_coverage_distribution'  => $coverageDistribution['method'],
            ],
        );

        try {
            $template->renderTo($file);
        } catch (Exception $e) {
            throw new FileCouldNotBeWrittenException(
                $e->getMessage(),
                $e->getCode(),
                $e,
            );
        }
    }

    protected function activeBreadcrumb(AbstractNode $node): string
    {
        return sprintf(
            '         <li class="breadcrumb-item"><a href="index.html">%s</a></li>' . "\n" .
            '         <li class="breadcrumb-item active">(Dashboard)</li>' . "\n",
            $node->name(),
        );
    }

    /**
     * Returns the data for the Class/Method Complexity charts.
     */
    private function complexity(array $classes, string $baseLink): array
    {
        $result = ['class' => [], 'method' => []];

        foreach ($classes as $className => $class) {
            foreach ($class['methods'] as $methodName => $method) {
                if ($className !== '*') {
                    $methodName = $className . '::' . $methodName;
                }

                $result['method'][] = [
                    $method['coverage'],
                    $method['ccn'],
                    sprintf(
                        '<a href="%s">%s</a>',
                        str_replace($baseLink, '', $method['link']),
                        $methodName,
                    ),
                ];
            }

            $result['class'][] = [
                $class['coverage'],
                $class['ccn'],
                sprintf(
                    '<a href="%s">%s</a>',
                    str_replace($baseLink, '', $class['link']),
                    $className,
                ),
            ];
        }

        return [
            'class'  => json_encode($result['class']),
            'method' => json_encode($result['method']),
        ];
    }

    /**
     * Returns the data for the Class / Method Coverage Distribution chart.
     */
    private function coverageDistribution(array $classes): array
    {
        $result = [
            'class' => [
                '0%'      => 0,
                '0-10%'   => 0,
                '10-20%'  => 0,
                '20-30%'  => 0,
                '30-40%'  => 0,
                '40-50%'  => 0,
                '50-60%'  => 0,
                '60-70%'  => 0,
                '70-80%'  => 0,
                '80-90%'  => 0,
                '90-100%' => 0,
                '100%'    => 0,
            ],
            'method' => [
                '0%'      => 0,
                '0-10%'   => 0,
                '10-20%'  => 0,
                '20-30%'  => 0,
                '30-40%'  => 0,
                '40-50%'  => 0,
                '50-60%'  => 0,
                '60-70%'  => 0,
                '70-80%'  => 0,
                '80-90%'  => 0,
                '90-100%' => 0,
                '100%'    => 0,
            ],
        ];

        foreach ($classes as $class) {
            foreach ($class['methods'] as $methodName => $method) {
                if ($method['coverage'] === 0) {
                    $result['method']['0%']++;
                } elseif ($method['coverage'] === 100) {
                    $result['method']['100%']++;
                } else {
                    $key = floor($method['coverage'] / 10) * 10;
                    $key = $key . '-' . ($key + 10) . '%';
                    $result['method'][$key]++;
                }
            }

            if ($class['coverage'] === 0) {
                $result['class']['0%']++;
            } elseif ($class['coverage'] === 100) {
                $result['class']['100%']++;
            } else {
                $key = floor($class['coverage'] / 10) * 10;
                $key = $key . '-' . ($key + 10) . '%';
                $result['class'][$key]++;
            }
        }

        return [
            'class'  => json_encode(array_values($result['class'])),
            'method' => json_encode(array_values($result['method'])),
        ];
    }

    /**
     * Returns the classes / methods with insufficient coverage.
     */
    private function insufficientCoverage(array $classes, string $baseLink): array
    {
        $leastTestedClasses = [];
        $leastTestedMethods = [];
        $result             = ['class' => '', 'method' => ''];

        foreach ($classes as $className => $class) {
            foreach ($class['methods'] as $methodName => $method) {
                if ($method['coverage'] < $this->thresholds->highLowerBound()) {
                    $key = $methodName;

                    if ($className !== '*') {
                        $key = $className . '::' . $methodName;
                    }

                    $leastTestedMethods[$key] = $method['coverage'];
                }
            }

            if ($class['coverage'] < $this->thresholds->highLowerBound()) {
                $leastTestedClasses[$className] = $class['coverage'];
            }
        }

        asort($leastTestedClasses);
        asort($leastTestedMethods);

        foreach ($leastTestedClasses as $className => $coverage) {
            $result['class'] .= sprintf(
                '       <tr><td><a href="%s">%s</a></td><td class="text-right">%d%%</td></tr>' . "\n",
                str_replace($baseLink, '', $classes[$className]['link']),
                $className,
                $coverage,
            );
        }

        foreach ($leastTestedMethods as $methodName => $coverage) {
            [$class, $method] = explode('::', $methodName);

            $result['method'] .= sprintf(
                '       <tr><td><a href="%s"><abbr title="%s">%s</abbr></a></td><td class="text-right">%d%%</td></tr>' . "\n",
                str_replace($baseLink, '', $classes[$class]['methods'][$method]['link']),
                $methodName,
                $method,
                $coverage,
            );
        }

        return $result;
    }

    /**
     * Returns the project risks according to the CRAP index.
     */
    private function projectRisks(array $classes, string $baseLink): array
    {
        $classRisks  = [];
        $methodRisks = [];
        $result      = ['class' => '', 'method' => ''];

        foreach ($classes as $className => $class) {
            foreach ($class['methods'] as $methodName => $method) {
                if ($method['coverage'] < $this->thresholds->highLowerBound() && $method['ccn'] > 1) {
                    $key = $methodName;

                    if ($className !== '*') {
                        $key = $className . '::' . $methodName;
                    }

                    $methodRisks[$key] = $method['crap'];
                }
            }

            if ($class['coverage'] < $this->thresholds->highLowerBound() &&
                $class['ccn'] > count($class['methods'])) {
                $classRisks[$className] = $class['crap'];
            }
        }

        arsort($classRisks);
        arsort($methodRisks);

        foreach ($classRisks as $className => $crap) {
            $result['class'] .= sprintf(
                '       <tr><td><a href="%s">%s</a></td><td class="text-right">%d</td></tr>' . "\n",
                str_replace($baseLink, '', $classes[$className]['link']),
                $className,
                $crap,
            );
        }

        foreach ($methodRisks as $methodName => $crap) {
            [$class, $method] = explode('::', $methodName);

            $result['method'] .= sprintf(
                '       <tr><td><a href="%s"><abbr title="%s">%s</abbr></a></td><td class="text-right">%d</td></tr>' . "\n",
                str_replace($baseLink, '', $classes[$class]['methods'][$method]['link']),
                $methodName,
                $method,
                $crap,
            );
        }

        return $result;
    }
}
PKE:]njU�9�9�Report/Html/Renderer/File.phpnu�[���<?php declare(strict_types=1);
/*
 * This file is part of phpunit/php-code-coverage.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace SebastianBergmann\CodeCoverage\Report\Html;

use const ENT_COMPAT;
use const ENT_HTML401;
use const ENT_SUBSTITUTE;
use const T_ABSTRACT;
use const T_ARRAY;
use const T_AS;
use const T_BREAK;
use const T_CALLABLE;
use const T_CASE;
use const T_CATCH;
use const T_CLASS;
use const T_CLONE;
use const T_COMMENT;
use const T_CONST;
use const T_CONTINUE;
use const T_DECLARE;
use const T_DEFAULT;
use const T_DO;
use const T_DOC_COMMENT;
use const T_ECHO;
use const T_ELSE;
use const T_ELSEIF;
use const T_EMPTY;
use const T_ENDDECLARE;
use const T_ENDFOR;
use const T_ENDFOREACH;
use const T_ENDIF;
use const T_ENDSWITCH;
use const T_ENDWHILE;
use const T_EVAL;
use const T_EXIT;
use const T_EXTENDS;
use const T_FINAL;
use const T_FINALLY;
use const T_FOR;
use const T_FOREACH;
use const T_FUNCTION;
use const T_GLOBAL;
use const T_GOTO;
use const T_HALT_COMPILER;
use const T_IF;
use const T_IMPLEMENTS;
use const T_INCLUDE;
use const T_INCLUDE_ONCE;
use const T_INLINE_HTML;
use const T_INSTANCEOF;
use const T_INSTEADOF;
use const T_INTERFACE;
use const T_ISSET;
use const T_LIST;
use const T_NAMESPACE;
use const T_NEW;
use const T_PRINT;
use const T_PRIVATE;
use const T_PROTECTED;
use const T_PUBLIC;
use const T_REQUIRE;
use const T_REQUIRE_ONCE;
use const T_RETURN;
use const T_STATIC;
use const T_SWITCH;
use const T_THROW;
use const T_TRAIT;
use const T_TRY;
use const T_UNSET;
use const T_USE;
use const T_VAR;
use const T_WHILE;
use const T_YIELD;
use const T_YIELD_FROM;
use function array_key_exists;
use function array_keys;
use function array_merge;
use function array_pop;
use function array_unique;
use function count;
use function explode;
use function file_get_contents;
use function htmlspecialchars;
use function is_string;
use function ksort;
use function range;
use function sort;
use function sprintf;
use function str_ends_with;
use function str_replace;
use function token_get_all;
use function trim;
use SebastianBergmann\CodeCoverage\FileCouldNotBeWrittenException;
use SebastianBergmann\CodeCoverage\Node\File as FileNode;
use SebastianBergmann\CodeCoverage\Util\Percentage;
use SebastianBergmann\Template\Exception;
use SebastianBergmann\Template\Template;

/**
 * @internal This class is not covered by the backward compatibility promise for phpunit/php-code-coverage
 */
final class File extends Renderer
{
    /**
     * @psalm-var array<int,true>
     */
    private const KEYWORD_TOKENS = [
        T_ABSTRACT      => true,
        T_ARRAY         => true,
        T_AS            => true,
        T_BREAK         => true,
        T_CALLABLE      => true,
        T_CASE          => true,
        T_CATCH         => true,
        T_CLASS         => true,
        T_CLONE         => true,
        T_CONST         => true,
        T_CONTINUE      => true,
        T_DECLARE       => true,
        T_DEFAULT       => true,
        T_DO            => true,
        T_ECHO          => true,
        T_ELSE          => true,
        T_ELSEIF        => true,
        T_EMPTY         => true,
        T_ENDDECLARE    => true,
        T_ENDFOR        => true,
        T_ENDFOREACH    => true,
        T_ENDIF         => true,
        T_ENDSWITCH     => true,
        T_ENDWHILE      => true,
        T_ENUM          => true,
        T_EVAL          => true,
        T_EXIT          => true,
        T_EXTENDS       => true,
        T_FINAL         => true,
        T_FINALLY       => true,
        T_FN            => true,
        T_FOR           => true,
        T_FOREACH       => true,
        T_FUNCTION      => true,
        T_GLOBAL        => true,
        T_GOTO          => true,
        T_HALT_COMPILER => true,
        T_IF            => true,
        T_IMPLEMENTS    => true,
        T_INCLUDE       => true,
        T_INCLUDE_ONCE  => true,
        T_INSTANCEOF    => true,
        T_INSTEADOF     => true,
        T_INTERFACE     => true,
        T_ISSET         => true,
        T_LIST          => true,
        T_MATCH         => true,
        T_NAMESPACE     => true,
        T_NEW           => true,
        T_PRINT         => true,
        T_PRIVATE       => true,
        T_PROTECTED     => true,
        T_PUBLIC        => true,
        T_READONLY      => true,
        T_REQUIRE       => true,
        T_REQUIRE_ONCE  => true,
        T_RETURN        => true,
        T_STATIC        => true,
        T_SWITCH        => true,
        T_THROW         => true,
        T_TRAIT         => true,
        T_TRY           => true,
        T_UNSET         => true,
        T_USE           => true,
        T_VAR           => true,
        T_WHILE         => true,
        T_YIELD         => true,
        T_YIELD_FROM    => true,
    ];
    private static array $formattedSourceCache = [];
    private int $htmlSpecialCharsFlags         = ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE;

    public function render(FileNode $node, string $file): void
    {
        $templateName = $this->templatePath . ($this->hasBranchCoverage ? 'file_branch.html' : 'file.html');
        $template     = new Template($templateName, '{{', '}}');
        $this->setCommonTemplateVariables($template, $node);

        $template->setVar(
            [
                'items'     => $this->renderItems($node),
                'lines'     => $this->renderSourceWithLineCoverage($node),
                'legend'    => '<p><span class="legend covered-by-small-tests">Covered by small (and larger) tests</span><span class="legend covered-by-medium-tests">Covered by medium (and large) tests</span><span class="legend covered-by-large-tests">Covered by large tests (and tests of unknown size)</span><span class="legend not-covered">Not covered</span><span class="legend not-coverable">Not coverable</span></p>',
                'structure' => '',
            ],
        );

        try {
            $template->renderTo($file . '.html');
        } catch (Exception $e) {
            throw new FileCouldNotBeWrittenException(
                $e->getMessage(),
                $e->getCode(),
                $e,
            );
        }

        if ($this->hasBranchCoverage) {
            $template->setVar(
                [
                    'items'     => $this->renderItems($node),
                    'lines'     => $this->renderSourceWithBranchCoverage($node),
                    'legend'    => '<p><span class="success"><strong>Fully covered</strong></span><span class="warning"><strong>Partially covered</strong></span><span class="danger"><strong>Not covered</strong></span></p>',
                    'structure' => $this->renderBranchStructure($node),
                ],
            );

            try {
                $template->renderTo($file . '_branch.html');
            } catch (Exception $e) {
                throw new FileCouldNotBeWrittenException(
                    $e->getMessage(),
                    $e->getCode(),
                    $e,
                );
            }

            $template->setVar(
                [
                    'items'     => $this->renderItems($node),
                    'lines'     => $this->renderSourceWithPathCoverage($node),
                    'legend'    => '<p><span class="success"><strong>Fully covered</strong></span><span class="warning"><strong>Partially covered</strong></span><span class="danger"><strong>Not covered</strong></span></p>',
                    'structure' => $this->renderPathStructure($node),
                ],
            );

            try {
                $template->renderTo($file . '_path.html');
            } catch (Exception $e) {
                throw new FileCouldNotBeWrittenException(
                    $e->getMessage(),
                    $e->getCode(),
                    $e,
                );
            }
        }
    }

    private function renderItems(FileNode $node): string
    {
        $templateName = $this->templatePath . ($this->hasBranchCoverage ? 'file_item_branch.html' : 'file_item.html');
        $template     = new Template($templateName, '{{', '}}');

        $methodTemplateName = $this->templatePath . ($this->hasBranchCoverage ? 'method_item_branch.html' : 'method_item.html');
        $methodItemTemplate = new Template(
            $methodTemplateName,
            '{{',
            '}}',
        );

        $items = $this->renderItemTemplate(
            $template,
            [
                'name'                            => 'Total',
                'numClasses'                      => $node->numberOfClassesAndTraits(),
                'numTestedClasses'                => $node->numberOfTestedClassesAndTraits(),
                'numMethods'                      => $node->numberOfFunctionsAndMethods(),
                'numTestedMethods'                => $node->numberOfTestedFunctionsAndMethods(),
                'linesExecutedPercent'            => $node->percentageOfExecutedLines()->asFloat(),
                'linesExecutedPercentAsString'    => $node->percentageOfExecutedLines()->asString(),
                'numExecutedLines'                => $node->numberOfExecutedLines(),
                'numExecutableLines'              => $node->numberOfExecutableLines(),
                'branchesExecutedPercent'         => $node->percentageOfExecutedBranches()->asFloat(),
                'branchesExecutedPercentAsString' => $node->percentageOfExecutedBranches()->asString(),
                'numExecutedBranches'             => $node->numberOfExecutedBranches(),
                'numExecutableBranches'           => $node->numberOfExecutableBranches(),
                'pathsExecutedPercent'            => $node->percentageOfExecutedPaths()->asFloat(),
                'pathsExecutedPercentAsString'    => $node->percentageOfExecutedPaths()->asString(),
                'numExecutedPaths'                => $node->numberOfExecutedPaths(),
                'numExecutablePaths'              => $node->numberOfExecutablePaths(),
                'testedMethodsPercent'            => $node->percentageOfTestedFunctionsAndMethods()->asFloat(),
                'testedMethodsPercentAsString'    => $node->percentageOfTestedFunctionsAndMethods()->asString(),
                'testedClassesPercent'            => $node->percentageOfTestedClassesAndTraits()->asFloat(),
                'testedClassesPercentAsString'    => $node->percentageOfTestedClassesAndTraits()->asString(),
                'crap'                            => '<abbr title="Change Risk Anti-Patterns (CRAP) Index">CRAP</abbr>',
            ],
        );

        $items .= $this->renderFunctionItems(
            $node->functions(),
            $methodItemTemplate,
        );

        $items .= $this->renderTraitOrClassItems(
            $node->traits(),
            $template,
            $methodItemTemplate,
        );

        $items .= $this->renderTraitOrClassItems(
            $node->classes(),
            $template,
            $methodItemTemplate,
        );

        return $items;
    }

    private function renderTraitOrClassItems(array $items, Template $template, Template $methodItemTemplate): string
    {
        $buffer = '';

        if (empty($items)) {
            return $buffer;
        }

        foreach ($items as $name => $item) {
            $numMethods       = 0;
            $numTestedMethods = 0;

            foreach ($item['methods'] as $method) {
                if ($method['executableLines'] > 0) {
                    $numMethods++;

                    if ($method['executedLines'] === $method['executableLines']) {
                        $numTestedMethods++;
                    }
                }
            }

            if ($item['executableLines'] > 0) {
                $numClasses                   = 1;
                $numTestedClasses             = $numTestedMethods === $numMethods ? 1 : 0;
                $linesExecutedPercentAsString = Percentage::fromFractionAndTotal(
                    $item['executedLines'],
                    $item['executableLines'],
                )->asString();
                $branchesExecutedPercentAsString = Percentage::fromFractionAndTotal(
                    $item['executedBranches'],
                    $item['executableBranches'],
                )->asString();
                $pathsExecutedPercentAsString = Percentage::fromFractionAndTotal(
                    $item['executedPaths'],
                    $item['executablePaths'],
                )->asString();
            } else {
                $numClasses                      = 0;
                $numTestedClasses                = 0;
                $linesExecutedPercentAsString    = 'n/a';
                $branchesExecutedPercentAsString = 'n/a';
                $pathsExecutedPercentAsString    = 'n/a';
            }

            $testedMethodsPercentage = Percentage::fromFractionAndTotal(
                $numTestedMethods,
                $numMethods,
            );

            $testedClassesPercentage = Percentage::fromFractionAndTotal(
                $numTestedMethods === $numMethods ? 1 : 0,
                1,
            );

            $buffer .= $this->renderItemTemplate(
                $template,
                [
                    'name'                 => $this->abbreviateClassName($name),
                    'numClasses'           => $numClasses,
                    'numTestedClasses'     => $numTestedClasses,
                    'numMethods'           => $numMethods,
                    'numTestedMethods'     => $numTestedMethods,
                    'linesExecutedPercent' => Percentage::fromFractionAndTotal(
                        $item['executedLines'],
                        $item['executableLines'],
                    )->asFloat(),
                    'linesExecutedPercentAsString' => $linesExecutedPercentAsString,
                    'numExecutedLines'             => $item['executedLines'],
                    'numExecutableLines'           => $item['executableLines'],
                    'branchesExecutedPercent'      => Percentage::fromFractionAndTotal(
                        $item['executedBranches'],
                        $item['executableBranches'],
                    )->asFloat(),
                    'branchesExecutedPercentAsString' => $branchesExecutedPercentAsString,
                    'numExecutedBranches'             => $item['executedBranches'],
                    'numExecutableBranches'           => $item['executableBranches'],
                    'pathsExecutedPercent'            => Percentage::fromFractionAndTotal(
                        $item['executedPaths'],
                        $item['executablePaths'],
                    )->asFloat(),
                    'pathsExecutedPercentAsString' => $pathsExecutedPercentAsString,
                    'numExecutedPaths'             => $item['executedPaths'],
                    'numExecutablePaths'           => $item['executablePaths'],
                    'testedMethodsPercent'         => $testedMethodsPercentage->asFloat(),
                    'testedMethodsPercentAsString' => $testedMethodsPercentage->asString(),
                    'testedClassesPercent'         => $testedClassesPercentage->asFloat(),
                    'testedClassesPercentAsString' => $testedClassesPercentage->asString(),
                    'crap'                         => $item['crap'],
                ],
            );

            foreach ($item['methods'] as $method) {
                $buffer .= $this->renderFunctionOrMethodItem(
                    $methodItemTemplate,
                    $method,
                    '&nbsp;',
                );
            }
        }

        return $buffer;
    }

    private function renderFunctionItems(array $functions, Template $template): string
    {
        if (empty($functions)) {
            return '';
        }

        $buffer = '';

        foreach ($functions as $function) {
            $buffer .= $this->renderFunctionOrMethodItem(
                $template,
                $function,
            );
        }

        return $buffer;
    }

    private function renderFunctionOrMethodItem(Template $template, array $item, string $indent = ''): string
    {
        $numMethods       = 0;
        $numTestedMethods = 0;

        if ($item['executableLines'] > 0) {
            $numMethods = 1;

            if ($item['executedLines'] === $item['executableLines']) {
                $numTestedMethods = 1;
            }
        }

        $executedLinesPercentage = Percentage::fromFractionAndTotal(
            $item['executedLines'],
            $item['executableLines'],
        );

        $executedBranchesPercentage = Percentage::fromFractionAndTotal(
            $item['executedBranches'],
            $item['executableBranches'],
        );

        $executedPathsPercentage = Percentage::fromFractionAndTotal(
            $item['executedPaths'],
            $item['executablePaths'],
        );

        $testedMethodsPercentage = Percentage::fromFractionAndTotal(
            $numTestedMethods,
            1,
        );

        return $this->renderItemTemplate(
            $template,
            [
                'name' => sprintf(
                    '%s<a href="#%d"><abbr title="%s">%s</abbr></a>',
                    $indent,
                    $item['startLine'],
                    htmlspecialchars($item['signature'], $this->htmlSpecialCharsFlags),
                    $item['functionName'] ?? $item['methodName'],
                ),
                'numMethods'                      => $numMethods,
                'numTestedMethods'                => $numTestedMethods,
                'linesExecutedPercent'            => $executedLinesPercentage->asFloat(),
                'linesExecutedPercentAsString'    => $executedLinesPercentage->asString(),
                'numExecutedLines'                => $item['executedLines'],
                'numExecutableLines'              => $item['executableLines'],
                'branchesExecutedPercent'         => $executedBranchesPercentage->asFloat(),
                'branchesExecutedPercentAsString' => $executedBranchesPercentage->asString(),
                'numExecutedBranches'             => $item['executedBranches'],
                'numExecutableBranches'           => $item['executableBranches'],
                'pathsExecutedPercent'            => $executedPathsPercentage->asFloat(),
                'pathsExecutedPercentAsString'    => $executedPathsPercentage->asString(),
                'numExecutedPaths'                => $item['executedPaths'],
                'numExecutablePaths'              => $item['executablePaths'],
                'testedMethodsPercent'            => $testedMethodsPercentage->asFloat(),
                'testedMethodsPercentAsString'    => $testedMethodsPercentage->asString(),
                'crap'                            => $item['crap'],
            ],
        );
    }

    private function renderSourceWithLineCoverage(FileNode $node): string
    {
        $linesTemplate      = new Template($this->templatePath . 'lines.html.dist', '{{', '}}');
        $singleLineTemplate = new Template($this->templatePath . 'line.html.dist', '{{', '}}');

        $coverageData = $node->lineCoverageData();
        $testData     = $node->testData();
        $codeLines    = $this->loadFile($node->pathAsString());
        $lines        = '';
        $i            = 1;

        foreach ($codeLines as $line) {
            $trClass        = '';
            $popoverContent = '';
            $popoverTitle   = '';

            if (array_key_exists($i, $coverageData)) {
                $numTests = ($coverageData[$i] ? count($coverageData[$i]) : 0);

                if ($coverageData[$i] === null) {
                    $trClass = 'warning';
                } elseif ($numTests === 0) {
                    $trClass = 'danger';
                } else {
                    if ($numTests > 1) {
                        $popoverTitle = $numTests . ' tests cover line ' . $i;
                    } else {
                        $popoverTitle = '1 test covers line ' . $i;
                    }

                    $lineCss        = 'covered-by-large-tests';
                    $popoverContent = '<ul>';

                    foreach ($coverageData[$i] as $test) {
                        if ($lineCss === 'covered-by-large-tests' && $testData[$test]['size'] === 'medium') {
                            $lineCss = 'covered-by-medium-tests';
                        } elseif ($testData[$test]['size'] === 'small') {
                            $lineCss = 'covered-by-small-tests';
                        }

                        $popoverContent .= $this->createPopoverContentForTest($test, $testData[$test]);
                    }

                    $popoverContent .= '</ul>';
                    $trClass = $lineCss . ' popin';
                }
            }

            $popover = '';

            if (!empty($popoverTitle)) {
                $popover = sprintf(
                    ' data-title="%s" data-content="%s" data-placement="top" data-html="true"',
                    $popoverTitle,
                    htmlspecialchars($popoverContent, $this->htmlSpecialCharsFlags),
                );
            }

            $lines .= $this->renderLine($singleLineTemplate, $i, $line, $trClass, $popover);

            $i++;
        }

        $linesTemplate->setVar(['lines' => $lines]);

        return $linesTemplate->render();
    }

    private function renderSourceWithBranchCoverage(FileNode $node): string
    {
        $linesTemplate      = new Template($this->templatePath . 'lines.html.dist', '{{', '}}');
        $singleLineTemplate = new Template($this->templatePath . 'line.html.dist', '{{', '}}');

        $functionCoverageData = $node->functionCoverageData();
        $testData             = $node->testData();
        $codeLines            = $this->loadFile($node->pathAsString());

        $lineData = [];

        /** @var int $line */
        foreach (array_keys($codeLines) as $line) {
            $lineData[$line + 1] = [
                'includedInBranches'    => 0,
                'includedInHitBranches' => 0,
                'tests'                 => [],
            ];
        }

        foreach ($functionCoverageData as $method) {
            foreach ($method['branches'] as $branch) {
                foreach (range($branch['line_start'], $branch['line_end']) as $line) {
                    if (!isset($lineData[$line])) { // blank line at end of file is sometimes included here
                        continue;
                    }

                    $lineData[$line]['includedInBranches']++;

                    if ($branch['hit']) {
                        $lineData[$line]['includedInHitBranches']++;
                        $lineData[$line]['tests'] = array_unique(array_merge($lineData[$line]['tests'], $branch['hit']));
                    }
                }
            }
        }

        $lines = '';
        $i     = 1;

        /** @var string $line */
        foreach ($codeLines as $line) {
            $trClass = '';
            $popover = '';

            if ($lineData[$i]['includedInBranches'] > 0) {
                $lineCss = 'success';

                if ($lineData[$i]['includedInHitBranches'] === 0) {
                    $lineCss = 'danger';
                } elseif ($lineData[$i]['includedInHitBranches'] !== $lineData[$i]['includedInBranches']) {
                    $lineCss = 'warning';
                }

                $popoverContent = '<ul>';

                if (count($lineData[$i]['tests']) === 1) {
                    $popoverTitle = '1 test covers line ' . $i;
                } else {
                    $popoverTitle = count($lineData[$i]['tests']) . ' tests cover line ' . $i;
                }
                $popoverTitle .= '. These are covering ' . $lineData[$i]['includedInHitBranches'] . ' out of the ' . $lineData[$i]['includedInBranches'] . ' code branches.';

                foreach ($lineData[$i]['tests'] as $test) {
                    $popoverContent .= $this->createPopoverContentForTest($test, $testData[$test]);
                }

                $popoverContent .= '</ul>';
                $trClass = $lineCss . ' popin';

                $popover = sprintf(
                    ' data-title="%s" data-content="%s" data-placement="top" data-html="true"',
                    $popoverTitle,
                    htmlspecialchars($popoverContent, $this->htmlSpecialCharsFlags),
                );
            }

            $lines .= $this->renderLine($singleLineTemplate, $i, $line, $trClass, $popover);

            $i++;
        }

        $linesTemplate->setVar(['lines' => $lines]);

        return $linesTemplate->render();
    }

    private function renderSourceWithPathCoverage(FileNode $node): string
    {
        $linesTemplate      = new Template($this->templatePath . 'lines.html.dist', '{{', '}}');
        $singleLineTemplate = new Template($this->templatePath . 'line.html.dist', '{{', '}}');

        $functionCoverageData = $node->functionCoverageData();
        $testData             = $node->testData();
        $codeLines            = $this->loadFile($node->pathAsString());

        $lineData = [];

        /** @var int $line */
        foreach (array_keys($codeLines) as $line) {
            $lineData[$line + 1] = [
                'includedInPaths'    => [],
                'includedInHitPaths' => [],
                'tests'              => [],
            ];
        }

        foreach ($functionCoverageData as $method) {
            foreach ($method['paths'] as $pathId => $path) {
                foreach ($path['path'] as $branchTaken) {
                    foreach (range($method['branches'][$branchTaken]['line_start'], $method['branches'][$branchTaken]['line_end']) as $line) {
                        if (!isset($lineData[$line])) {
                            continue;
                        }
                        $lineData[$line]['includedInPaths'][] = $pathId;

                        if ($path['hit']) {
                            $lineData[$line]['includedInHitPaths'][] = $pathId;
                            $lineData[$line]['tests']                = array_unique(array_merge($lineData[$line]['tests'], $path['hit']));
                        }
                    }
                }
            }
        }

        $lines = '';
        $i     = 1;

        /** @var string $line */
        foreach ($codeLines as $line) {
            $trClass                 = '';
            $popover                 = '';
            $includedInPathsCount    = count(array_unique($lineData[$i]['includedInPaths']));
            $includedInHitPathsCount = count(array_unique($lineData[$i]['includedInHitPaths']));

            if ($includedInPathsCount > 0) {
                $lineCss = 'success';

                if ($includedInHitPathsCount === 0) {
                    $lineCss = 'danger';
                } elseif ($includedInHitPathsCount !== $includedInPathsCount) {
                    $lineCss = 'warning';
                }

                $popoverContent = '<ul>';

                if (count($lineData[$i]['tests']) === 1) {
                    $popoverTitle = '1 test covers line ' . $i;
                } else {
                    $popoverTitle = count($lineData[$i]['tests']) . ' tests cover line ' . $i;
                }
                $popoverTitle .= '. These are covering ' . $includedInHitPathsCount . ' out of the ' . $includedInPathsCount . ' code paths.';

                foreach ($lineData[$i]['tests'] as $test) {
                    $popoverContent .= $this->createPopoverContentForTest($test, $testData[$test]);
                }

                $popoverContent .= '</ul>';
                $trClass = $lineCss . ' popin';

                $popover = sprintf(
                    ' data-title="%s" data-content="%s" data-placement="top" data-html="true"',
                    $popoverTitle,
                    htmlspecialchars($popoverContent, $this->htmlSpecialCharsFlags),
                );
            }

            $lines .= $this->renderLine($singleLineTemplate, $i, $line, $trClass, $popover);

            $i++;
        }

        $linesTemplate->setVar(['lines' => $lines]);

        return $linesTemplate->render();
    }

    private function renderBranchStructure(FileNode $node): string
    {
        $branchesTemplate = new Template($this->templatePath . 'branches.html.dist', '{{', '}}');

        $coverageData = $node->functionCoverageData();
        $testData     = $node->testData();
        $codeLines    = $this->loadFile($node->pathAsString());
        $branches     = '';

        ksort($coverageData);

        foreach ($coverageData as $methodName => $methodData) {
            if (!$methodData['branches']) {
                continue;
            }

            $branchStructure = '';

            foreach ($methodData['branches'] as $branch) {
                $branchStructure .= $this->renderBranchLines($branch, $codeLines, $testData);
            }

            if ($branchStructure !== '') { // don't show empty branches
                $branches .= '<h5 class="structure-heading"><a name="' . htmlspecialchars($methodName, $this->htmlSpecialCharsFlags) . '">' . $this->abbreviateMethodName($methodName) . '</a></h5>' . "\n";
                $branches .= $branchStructure;
            }
        }

        $branchesTemplate->setVar(['branches' => $branches]);

        return $branchesTemplate->render();
    }

    private function renderBranchLines(array $branch, array $codeLines, array $testData): string
    {
        $linesTemplate      = new Template($this->templatePath . 'lines.html.dist', '{{', '}}');
        $singleLineTemplate = new Template($this->templatePath . 'line.html.dist', '{{', '}}');

        $lines = '';

        $branchLines = range($branch['line_start'], $branch['line_end']);
        sort($branchLines); // sometimes end_line < start_line

        /** @var int $line */
        foreach ($branchLines as $line) {
            if (!isset($codeLines[$line])) { // blank line at end of file is sometimes included here
                continue;
            }

            $popoverContent = '';
            $popoverTitle   = '';

            $numTests = count($branch['hit']);

            if ($numTests === 0) {
                $trClass = 'danger';
            } else {
                $lineCss        = 'covered-by-large-tests';
                $popoverContent = '<ul>';

                if ($numTests > 1) {
                    $popoverTitle = $numTests . ' tests cover this branch';
                } else {
                    $popoverTitle = '1 test covers this branch';
                }

                foreach ($branch['hit'] as $test) {
                    if ($lineCss === 'covered-by-large-tests' && $testData[$test]['size'] === 'medium') {
                        $lineCss = 'covered-by-medium-tests';
                    } elseif ($testData[$test]['size'] === 'small') {
                        $lineCss = 'covered-by-small-tests';
                    }

                    $popoverContent .= $this->createPopoverContentForTest($test, $testData[$test]);
                }
                $trClass = $lineCss . ' popin';
            }

            $popover = '';

            if (!empty($popoverTitle)) {
                $popover = sprintf(
                    ' data-title="%s" data-content="%s" data-placement="top" data-html="true"',
                    $popoverTitle,
                    htmlspecialchars($popoverContent, $this->htmlSpecialCharsFlags),
                );
            }

            $lines .= $this->renderLine($singleLineTemplate, $line, $codeLines[$line - 1], $trClass, $popover);
        }

        if ($lines === '') {
            return '';
        }

        $linesTemplate->setVar(['lines' => $lines]);

        return $linesTemplate->render();
    }

    private function renderPathStructure(FileNode $node): string
    {
        $pathsTemplate = new Template($this->templatePath . 'paths.html.dist', '{{', '}}');

        $coverageData = $node->functionCoverageData();
        $testData     = $node->testData();
        $codeLines    = $this->loadFile($node->pathAsString());
        $paths        = '';

        ksort($coverageData);

        foreach ($coverageData as $methodName => $methodData) {
            if (!$methodData['paths']) {
                continue;
            }

            $pathStructure = '';

            if (count($methodData['paths']) > 100) {
                $pathStructure .= '<p>' . count($methodData['paths']) . ' is too many paths to sensibly render, consider refactoring your code to bring this number down.</p>';

                continue;
            }

            foreach ($methodData['paths'] as $path) {
                $pathStructure .= $this->renderPathLines($path, $methodData['branches'], $codeLines, $testData);
            }

            if ($pathStructure !== '') {
                $paths .= '<h5 class="structure-heading"><a name="' . htmlspecialchars($methodName, $this->htmlSpecialCharsFlags) . '">' . $this->abbreviateMethodName($methodName) . '</a></h5>' . "\n";
                $paths .= $pathStructure;
            }
        }

        $pathsTemplate->setVar(['paths' => $paths]);

        return $pathsTemplate->render();
    }

    private function renderPathLines(array $path, array $branches, array $codeLines, array $testData): string
    {
        $linesTemplate      = new Template($this->templatePath . 'lines.html.dist', '{{', '}}');
        $singleLineTemplate = new Template($this->templatePath . 'line.html.dist', '{{', '}}');

        $lines = '';
        $first = true;

        foreach ($path['path'] as $branchId) {
            if ($first) {
                $first = false;
            } else {
                $lines .= '    <tr><td colspan="2">&nbsp;</td></tr>' . "\n";
            }

            $branchLines = range($branches[$branchId]['line_start'], $branches[$branchId]['line_end']);
            sort($branchLines); // sometimes end_line < start_line

            /** @var int $line */
            foreach ($branchLines as $line) {
                if (!isset($codeLines[$line])) { // blank line at end of file is sometimes included here
                    continue;
                }

                $popoverContent = '';
                $popoverTitle   = '';

                $numTests = count($path['hit']);

                if ($numTests === 0) {
                    $trClass = 'danger';
                } else {
                    $lineCss        = 'covered-by-large-tests';
                    $popoverContent = '<ul>';

                    if ($numTests > 1) {
                        $popoverTitle = $numTests . ' tests cover this path';
                    } else {
                        $popoverTitle = '1 test covers this path';
                    }

                    foreach ($path['hit'] as $test) {
                        if ($lineCss === 'covered-by-large-tests' && $testData[$test]['size'] === 'medium') {
                            $lineCss = 'covered-by-medium-tests';
                        } elseif ($testData[$test]['size'] === 'small') {
                            $lineCss = 'covered-by-small-tests';
                        }

                        $popoverContent .= $this->createPopoverContentForTest($test, $testData[$test]);
                    }

                    $trClass = $lineCss . ' popin';
                }

                $popover = '';

                if (!empty($popoverTitle)) {
                    $popover = sprintf(
                        ' data-title="%s" data-content="%s" data-placement="top" data-html="true"',
                        $popoverTitle,
                        htmlspecialchars($popoverContent, $this->htmlSpecialCharsFlags),
                    );
                }

                $lines .= $this->renderLine($singleLineTemplate, $line, $codeLines[$line - 1], $trClass, $popover);
            }
        }

        if ($lines === '') {
            return '';
        }

        $linesTemplate->setVar(['lines' => $lines]);

        return $linesTemplate->render();
    }

    private function renderLine(Template $template, int $lineNumber, string $lineContent, string $class, string $popover): string
    {
        $template->setVar(
            [
                'lineNumber'  => $lineNumber,
                'lineContent' => $lineContent,
                'class'       => $class,
                'popover'     => $popover,
            ],
        );

        return $template->render();
    }

    private function loadFile(string $file): array
    {
        if (isset(self::$formattedSourceCache[$file])) {
            return self::$formattedSourceCache[$file];
        }

        $buffer              = file_get_contents($file);
        $tokens              = token_get_all($buffer);
        $result              = [''];
        $i                   = 0;
        $stringFlag          = false;
        $fileEndsWithNewLine = str_ends_with($buffer, "\n");

        unset($buffer);

        foreach ($tokens as $j => $token) {
            if (is_string($token)) {
                if ($token === '"' && $tokens[$j - 1] !== '\\') {
                    $result[$i] .= sprintf(
                        '<span class="string">%s</span>',
                        htmlspecialchars($token, $this->htmlSpecialCharsFlags),
                    );

                    $stringFlag = !$stringFlag;
                } else {
                    $result[$i] .= sprintf(
                        '<span class="keyword">%s</span>',
                        htmlspecialchars($token, $this->htmlSpecialCharsFlags),
                    );
                }

                continue;
            }

            [$token, $value] = $token;

            $value = str_replace(
                ["\t", ' '],
                ['&nbsp;&nbsp;&nbsp;&nbsp;', '&nbsp;'],
                htmlspecialchars($value, $this->htmlSpecialCharsFlags),
            );

            if ($value === "\n") {
                $result[++$i] = '';
            } else {
                $lines = explode("\n", $value);

                foreach ($lines as $jj => $line) {
                    $line = trim($line);

                    if ($line !== '') {
                        if ($stringFlag) {
                            $colour = 'string';
                        } else {
                            $colour = 'default';

                            if ($this->isInlineHtml($token)) {
                                $colour = 'html';
                            } elseif ($this->isComment($token)) {
                                $colour = 'comment';
                            } elseif ($this->isKeyword($token)) {
                                $colour = 'keyword';
                            }
                        }

                        $result[$i] .= sprintf(
                            '<span class="%s">%s</span>',
                            $colour,
                            $line,
                        );
                    }

                    if (isset($lines[$jj + 1])) {
                        $result[++$i] = '';
                    }
                }
            }
        }

        if ($fileEndsWithNewLine) {
            unset($result[count($result) - 1]);
        }

        self::$formattedSourceCache[$file] = $result;

        return $result;
    }

    private function abbreviateClassName(string $className): string
    {
        $tmp = explode('\\', $className);

        if (count($tmp) > 1) {
            $className = sprintf(
                '<abbr title="%s">%s</abbr>',
                $className,
                array_pop($tmp),
            );
        }

        return $className;
    }

    private function abbreviateMethodName(string $methodName): string
    {
        $parts = explode('->', $methodName);

        if (count($parts) === 2) {
            return $this->abbreviateClassName($parts[0]) . '->' . $parts[1];
        }

        return $methodName;
    }

    private function createPopoverContentForTest(string $test, array $testData): string
    {
        $testCSS = '';

        switch ($testData['status']) {
            case 'success':
                $testCSS = match ($testData['size']) {
                    'small'  => ' class="covered-by-small-tests"',
                    'medium' => ' class="covered-by-medium-tests"',
                    // no break
                    default => ' class="covered-by-large-tests"',
                };

                break;

            case 'failure':
                $testCSS = ' class="danger"';

                break;
        }

        return sprintf(
            '<li%s>%s</li>',
            $testCSS,
            htmlspecialchars($test, $this->htmlSpecialCharsFlags),
        );
    }

    private function isComment(int $token): bool
    {
        return $token === T_COMMENT || $token === T_DOC_COMMENT;
    }

    private function isInlineHtml(int $token): bool
    {
        return $token === T_INLINE_HTML;
    }

    private function isKeyword(int $token): bool
    {
        return isset(self::KEYWORD_TOKENS[$token]);
    }
}
PKE:]��D��8Report/Html/Renderer/Template/dashboard_branch.html.distnu�[���<!DOCTYPE html>
<html lang="en">
 <head>
  <meta charset="UTF-8">
  <title>Dashboard for {{full_path}}</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link href="{{path_to_root}}_css/bootstrap.min.css?v={{version}}" rel="stylesheet" type="text/css">
  <link href="{{path_to_root}}_css/nv.d3.min.css?v={{version}}" rel="stylesheet" type="text/css">
  <link href="{{path_to_root}}_css/style.css?v={{version}}" rel="stylesheet" type="text/css">
  <link href="{{path_to_root}}_css/custom.css" rel="stylesheet" type="text/css">
 </head>
 <body>
  <header>
   <div class="container-fluid">
    <div class="row">
     <div class="col-md-12">
      <nav aria-label="breadcrumb">
       <ol class="breadcrumb">
{{breadcrumbs}}
       </ol>
      </nav>
     </div>
    </div>
   </div>
  </header>
  <div class="container-fluid">
   <div class="row">
    <div class="col-md-12">
     <h2>Classes</h2>
    </div>
   </div>
   <div class="row">
    <div class="col-md-6">
     <h3>Coverage Distribution</h3>
     <div id="classCoverageDistribution" style="height: 300px;">
       <svg></svg>
     </div>
    </div>
    <div class="col-md-6">
     <h3>Complexity</h3>
     <div id="classComplexity" style="height: 300px;">
       <svg></svg>
     </div>
    </div>
   </div>
   <div class="row">
    <div class="col-md-6">
     <h3>Insufficient Coverage</h3>
     <div class="scrollbox">
      <table class="table">
       <thead>
        <tr>
         <th>Class</th>
         <th class="text-right">Coverage</th>
        </tr>
       </thead>
       <tbody>
{{insufficient_coverage_classes}}
       </tbody>
      </table>
     </div>
    </div>
    <div class="col-md-6">
     <h3>Project Risks</h3>
     <div class="scrollbox">
      <table class="table">
       <thead>
        <tr>
         <th>Class</th>
         <th class="text-right"><abbr title="Change Risk Anti-Patterns (CRAP) Index">CRAP</abbr></th>
        </tr>
       </thead>
       <tbody>
{{project_risks_classes}}
       </tbody>
      </table>
     </div>
    </div>
   </div>
   <div class="row">
    <div class="col-md-12">
     <h2>Methods</h2>
    </div>
   </div>
   <div class="row">
    <div class="col-md-6">
     <h3>Coverage Distribution</h3>
     <div id="methodCoverageDistribution" style="height: 300px;">
       <svg></svg>
     </div>
    </div>
    <div class="col-md-6">
     <h3>Complexity</h3>
     <div id="methodComplexity" style="height: 300px;">
       <svg></svg>
     </div>
    </div>
   </div>
   <div class="row">
    <div class="col-md-6">
     <h3>Insufficient Coverage</h3>
     <div class="scrollbox">
      <table class="table">
       <thead>
        <tr>
         <th>Method</th>
         <th class="text-right">Coverage</th>
        </tr>
       </thead>
       <tbody>
{{insufficient_coverage_methods}}
       </tbody>
      </table>
     </div>
    </div>
    <div class="col-md-6">
     <h3>Project Risks</h3>
     <div class="scrollbox">
      <table class="table">
       <thead>
        <tr>
         <th>Method</th>
         <th class="text-right"><abbr title="Change Risk Anti-Patterns (CRAP) Index">CRAP</abbr></th>
        </tr>
       </thead>
       <tbody>
{{project_risks_methods}}
       </tbody>
      </table>
     </div>
    </div>
   </div>
   <footer>
    <hr/>
    <p>
     <small>Generated by <a href="https://github.com/sebastianbergmann/php-code-coverage" target="_top">php-code-coverage {{version}}</a> using {{runtime}}{{generator}} at {{date}}.</small>
    </p>
   </footer>
  </div>
  <script src="{{path_to_root}}_js/jquery.min.js?v={{version}}" type="text/javascript"></script>
  <script src="{{path_to_root}}_js/d3.min.js?v={{version}}" type="text/javascript"></script>
  <script src="{{path_to_root}}_js/nv.d3.min.js?v={{version}}" type="text/javascript"></script>
  <script type="text/javascript">
$(document).ready(function() {
  nv.addGraph(function() {
    var chart = nv.models.multiBarChart();
    chart.tooltips(false)
      .showControls(false)
      .showLegend(false)
      .reduceXTicks(false)
      .staggerLabels(true)
      .yAxis.tickFormat(d3.format('d'));

    d3.select('#classCoverageDistribution svg')
      .datum(getCoverageDistributionData({{class_coverage_distribution}}, "Class Coverage"))
      .transition().duration(500).call(chart);

    nv.utils.windowResize(chart.update);

    return chart;
  });

  nv.addGraph(function() {
    var chart = nv.models.multiBarChart();
    chart.tooltips(false)
      .showControls(false)
      .showLegend(false)
      .reduceXTicks(false)
      .staggerLabels(true)
      .yAxis.tickFormat(d3.format('d'));

    d3.select('#methodCoverageDistribution svg')
      .datum(ge