diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index c0c98622327c..1e075ac95657 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -2,7 +2,7 @@
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
-open_collective: yetiforcecrm
+open_collective: # Replace with a single open_collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
diff --git a/.github/ISSUE_TEMPLATE/1-bug-report.md b/.github/ISSUE_TEMPLATE/1-bug-report.md
index d9e5024d6cb2..2088d87aa2ff 100644
--- a/.github/ISSUE_TEMPLATE/1-bug-report.md
+++ b/.github/ISSUE_TEMPLATE/1-bug-report.md
@@ -13,7 +13,7 @@ Oh hi there! 😄
To expedite issue processing please search open and closed issues before submitting a new one.
Existing issues often contain information about workarounds, resolution, or progress updates.
-Before you create a new issue, please check out our [manual] (https://yetiforce.com/en/knowledge-base/documentation/implementer-documentation/item/how-to-report-bugs)
+Before you create a new issue, please check out our [manual] (https://doc.yetiforce.com/developer-guides/github/how-to-report-bugs)
🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅-->
@@ -63,7 +63,7 @@ Please include a screenshot of your configuration. Here is an example: https://p
@@ -57,7 +57,7 @@ Steps to reproduce the behavior:
/u', function ($matches) {
- return $matches[1] ?? '';
- }, $content);
+ $content = preg_replace_callback('//u', fn ($matches) => $matches[1] ?? '', $content);
$twig = new \Twig\Environment(new \Twig\Loader\ArrayLoader(['index' => $content]));
$sandbox = new \Twig\Extension\SandboxExtension(\App\Extension\Twig\SecurityPolicy::getPolicy(), true);
$twig->addExtension($sandbox);
@@ -560,20 +561,19 @@ protected function organization(string $params): string
} else {
[$id, $fieldName, $params] = array_pad(explode('|', $params, 3), 3, false);
}
- if (!Record::isExists($id, 'MultiCompany')) {
- return '';
- }
- $recordModel = \Vtiger_Record_Model::getInstanceById($id, 'MultiCompany');
- if ($recordModel->has($fieldName)) {
- $value = $recordModel->get($fieldName);
- $fieldModel = $recordModel->getModule()->getFieldByName($fieldName);
- if ('' === $value || !$fieldModel || !$this->useValue($fieldModel, 'MultiCompany')) {
- return '';
- }
- if ($this->withoutTranslations) {
- $returnVal = $this->getDisplayValueByType($value, $recordModel, $fieldModel, $params);
- } else {
- $returnVal = $fieldModel->getUITypeModel()->getTextParserDisplayValue($value, $recordModel, $params);
+ if (Record::isExists($id, 'MultiCompany')) {
+ $companyRecordModel = \Vtiger_Record_Model::getInstanceById($id, 'MultiCompany');
+ if ($companyRecordModel->has($fieldName)) {
+ $value = $companyRecordModel->get($fieldName);
+ $fieldModel = $companyRecordModel->getModule()->getFieldByName($fieldName);
+ if ('' === $value || !$fieldModel || !$this->useValue($fieldModel, 'MultiCompany')) {
+ return '';
+ }
+ if ($this->withoutTranslations) {
+ $returnVal = $this->getDisplayValueByType($value, $companyRecordModel, $fieldModel, $params);
+ } else {
+ $returnVal = $fieldModel->getUITypeModel()->getTextParserDisplayValue($value, $companyRecordModel, $params);
+ }
}
}
return $returnVal;
@@ -628,7 +628,7 @@ protected function general($key)
return (new \DateTimeField(null))->getDisplayDate();
case 'CurrentTime':
return \Vtiger_Util_Helper::convertTimeIntoUsersDisplayFormat(date('H:i:s'));
- case 'CurrentDateTime':
+ case 'CurrentDateTime':
return Fields\DateTime::formatToDisplay('now');
case 'SiteUrl':
return Config::main('site_URL');
@@ -694,7 +694,8 @@ protected function record($params, $isPermitted = true)
$oldValue = $this->getDisplayValueByField($fieldModel, $oldValue);
$currentValue = $this->getDisplayValueByField($fieldModel);
if ($this->withoutTranslations) {
- $value .= "\$(translate : {$this->moduleName}|{$fieldModel->getFieldLabel()})\$ \$(translate : LBL_FROM)\$ $oldValue \$(translate : LBL_TO)\$ " . $currentValue . ($this->isHtml ? '
' : PHP_EOL);
+ $label = \App\Purifier::encodeHtml($fieldModel->getFieldLabel());
+ $value .= "\$(translate : {$this->moduleName}|{$label})\$ \$(translate : LBL_FROM)\$ $oldValue \$(translate : LBL_TO)\$ " . $currentValue . ($this->isHtml ? '
' : PHP_EOL);
} else {
$value .= Language::translate($fieldModel->getFieldLabel(), $this->moduleName, $this->language) . ' ';
$value .= Language::translate('LBL_FROM') . " $oldValue " . Language::translate('LBL_TO') . " $currentValue" . ($this->isHtml ? '
' : PHP_EOL);
@@ -715,7 +716,8 @@ protected function record($params, $isPermitted = true)
}
$currentValue = \in_array($fieldModel->getFieldDataType(), $this->largeDataUiTypes) ? '' : $this->getDisplayValueByField($fieldModel);
if ($this->withoutTranslations) {
- $value .= "\$(translate : {$this->moduleName}|{$fieldModel->getFieldLabel()})\$: $currentValue" . ($this->isHtml ? '
' : PHP_EOL);
+ $label = \App\Purifier::encodeHtml($fieldModel->getFieldLabel());
+ $value .= "\$(translate : {$this->moduleName}|{$label})\$: $currentValue" . ($this->isHtml ? '
' : PHP_EOL);
} else {
$value .= Language::translate($fieldModel->getFieldLabel(), $this->moduleName, $this->language) . ": $currentValue" . ($this->isHtml ? '
' : PHP_EOL);
}
@@ -728,7 +730,8 @@ protected function record($params, $isPermitted = true)
foreach ($fields as $fieldName => $fieldModel) {
$currentValue = $this->getDisplayValueByField($fieldModel);
if ($this->withoutTranslations) {
- $value .= "\$(translate : {$this->moduleName}|{$fieldModel->getFieldLabel()})\$: $currentValue" . ($this->isHtml ? '
' : PHP_EOL);
+ $label = \App\Purifier::encodeHtml($fieldModel->getFieldLabel());
+ $value .= "\$(translate : {$this->moduleName}|{$label})\$: $currentValue" . ($this->isHtml ? '
' : PHP_EOL);
} else {
$value .= Language::translate($fieldModel->getFieldLabel(), $this->moduleName, $this->language) . ": $currentValue" . ($this->isHtml ? '
' : PHP_EOL);
}
@@ -755,7 +758,11 @@ protected function record($params, $isPermitted = true)
*/
protected function relatedRecord($params)
{
- [$fieldName, $relatedField, $relatedModule] = array_pad(explode('|', $params, 3), 3, '');
+ $params = explode('|', $params);
+ $fieldName = array_shift($params);
+ $relatedField = array_shift($params);
+ $relatedModule = array_shift($params);
+ $value = $params ? $relatedField . '|' . implode('|', $params) : $relatedField;
if (
!isset($this->recordModel)
|| ($this->permissions && !Privilege::isPermitted($this->moduleName, 'DetailView', $this->record))
@@ -782,7 +789,7 @@ protected function relatedRecord($params)
$instance->{$key} = $this->{$key};
}
}
- $return[] = $instance->record($relatedField, false);
+ $return[] = $instance->record($value, false);
}
continue;
}
@@ -795,7 +802,7 @@ protected function relatedRecord($params)
$instance->{$key} = $this->{$key};
}
}
- $return[] = $instance->record($relatedField, false);
+ $return[] = $instance->record($value, false);
}
}
}
@@ -815,7 +822,7 @@ protected function relatedRecord($params)
$instance->{$key} = $this->{$key};
}
}
- return $instance->record($relatedField);
+ return $instance->record($value);
}
/**
@@ -925,6 +932,9 @@ protected function relatedRecordsList($params)
}
if ($columns) {
$relationListView->setFields($columns);
+ } else {
+ $fields = array_filter($relationListView->getHeaders(), fn ($fieldModel) => !$fieldModel->get('fromOutsideList'));
+ $relationListView->setFields(array_keys($fields));
}
if ($conditions) {
$transformedSearchParams = $relationListView->getQueryGenerator()->parseBaseSearchParamsToCondition(Json::decode($conditions));
@@ -946,11 +956,12 @@ protected function relatedRecordsListPrinter(\Vtiger_RelationListView_Model $rel
{
$relatedModuleName = $relationListView->getRelationModel()->getRelationModuleName();
$rows = $headers = '';
- $fields = $relationListView->getHeaders();
+ $fields = $relationListView->getRelationModel()->getQueryFields();
foreach ($fields as $fieldModel) {
- if ($fieldModel->isViewable()) {
+ if ($fieldModel->isViewable() || $fieldModel->get('fromOutsideList')) {
if ($this->withoutTranslations) {
- $headers .= "
' . Language::translate($field->get('label'), $this->moduleName) . ' | '; + $html .= '' . (empty($labels[$key]) ? Language::translate($field->get('label'), $this->moduleName) : Purifier::encodeHtml($labels[$key])) . ' | '; $columns[$field->getColumnName()] = $field; } $html .= '
---|
'.$differenceOfAmountsDesciption.' | +
---|
'.$differenceOfAmounts .' | +
"; + $html .= ' | ';
if ($fieldModel->isSummary()) {
$sum = 0;
foreach ($inventoryRows as $inventoryRow) {
diff --git a/app/TextParser/ProductsTableDescription.php b/app/TextParser/ProductsTableDescription.php
index a4faec1be340..18dcbe9ffeca 100644
--- a/app/TextParser/ProductsTableDescription.php
+++ b/app/TextParser/ProductsTableDescription.php
@@ -7,8 +7,8 @@
*
* @package TextParser
*
- * @copyright YetiForce Sp. z o.o
- * @license YetiForce Public License 4.0 (licenses/LicenseEN.txt or yetiforce.com)
+ * @copyright YetiForce S.A.
+ * @license YetiForce Public License 6.5 (licenses/LicenseEN.txt or yetiforce.com)
* @author Tomasz Kur
' . $key . '% |
- ' . \CurrencyField::convertToUserFormat($tax, null, true) . ' ' . $currencyData['currency_symbol'] . ' |
+ ' . \CurrencyField::convertToUserFormat($tax, null, true) . ' ' . $currencySymbol . ' |
' . \App\Language::translate('LBL_AMOUNT', $this->textParser->moduleName) . ' / ' . \App\Language::translate('LBL_AMOUNT', $this->textParser->moduleName, \App\Language::DEFAULT_LANG) . ' |
- ' . \CurrencyField::convertToUserFormat($taxAmount, null, true) . ' ' . $currencyData['currency_symbol'] . ' |
+ ' . \CurrencyField::convertToUserFormat($taxAmount, null, true) . ' ' . $currencySymbol . ' |
' . \App\Language::translate(($verify['message'] ?? 'LBL_YETIFORCE_SHOP_PRODUCT_CANCELED'), 'Settings::YetiForce') . ' '; - } - } + self::$productCache = []; + $this->success = false; + try { + $client = new ApiClient(); + $client->send(self::URL . '/' . \App\Version::get() . '/products', 'GET'); + $this->error = $client->getError(); + if (!$this->error && 200 === $client->getStatusCode() && !\App\Json::isEmpty($client->getResponseBody())) { + $this->setProducts(\App\Json::decode($client->getResponseBody())); + $this->success = true; } - } else { - foreach (self::getProducts() as $product) { - $verify = $product->verify(); - if (!$verify['status']) { - $products .= \App\Language::translate($product->getLabel(), 'Settings::YetiForce'); - if ($onlyNames) { - $products .= ','; - } else { - $products .= ' ' . \App\Language::translate(($verify['message'] ?? 'LBL_YETIFORCE_SHOP_PRODUCT_CANCELED'), 'Settings::YetiForce') . ' '; - } - } + } catch (\Throwable $e) { + $this->success = false; + $this->error = $e->getMessage(); + \App\Log::error($e->getMessage(), __METHOD__); + } + } + + /** + * Get product by ID. + * + * @param string $productId + * + * @return void + */ + public function loadProduct(string $productId) + { + $this->success = false; + try { + $client = new ApiClient(); + $client->send(self::URL . '/' . \App\Version::get() . "/products/{$productId}", 'GET'); + $this->error = $client->getError(); + if (!$this->error && 200 === $client->getStatusCode() && !\App\Json::isEmpty($client->getResponseBody())) { + $this->setProducts([\App\Json::decode($client->getResponseBody())]); + $this->success = true; } + } catch (\Throwable $e) { + $this->success = false; + $this->error = $e->getMessage(); + \App\Log::error($e->getMessage(), __METHOD__); } - return rtrim($products, ' ,'); } /** - * Generate cache. + * Get products. + * + * @param string $name + * @param string $section + * @param string $productId + * + * @return Shop\AbstractBaseProduct */ - public static function generateCache(): void + public static function getProduct(string $name, string $productId = ''): ?Shop\AbstractBaseProduct { - $content = []; - foreach (self::getProducts() as $product) { - $verify = $product->verify(); - if ($verify['status']) { - unset($verify['message']); + if (empty(self::$productCache[$name])) { + if ($productId) { + (new self())->loadProduct($productId); + } else { + (new self())->load(); } - $content['products'][$product->getLabel()] = $verify; } - $content['key'] = md5(json_encode($content['products'])); - \App\Utils::saveToFile(ROOT_DIRECTORY . '/app_data/shopCache.php', $content, 'Modifying this file will breach the licence terms!!!', 0, true); + + return self::$productCache[$name] ?? null; } /** - * Get from cache. + * Get product class. * - * @return array + * @param string $name + * + * @return string */ - public static function getFromCache(): array + private static function getProductClass(string $name): string { - $content = []; - if (\file_exists(ROOT_DIRECTORY . '/app_data/shopCache.php')) { - $content = include ROOT_DIRECTORY . '/app_data/shopCache.php'; + $className = '\\App\\YetiForce\\Shop\\Product\\' . $name; + if (!class_exists($className)) { + $className = '\\App\\YetiForce\\Shop\\Product\\YetiForceBase'; } - if (empty($content['products']) || ($content['key'] ?? '') !== md5(json_encode($content['products']))) { - $content['products'] = []; + return $className; + } + + /** + * Set Products to cache. + * + * @param array $products + * + * @return void + */ + private function setProducts(array $products) + { + foreach ($products as $productData) { + $name = $productData['name'] ?? ''; + $className = self::getProductClass($name); + if (!empty($productData['packages']) && ($product = $className::fromArray($productData)) && $product->isAvailable()) { + self::$productCache[$product->getName()] = $product; + } } - return $content['products']; } } diff --git a/app/YetiForce/Shop/AbstractBaseProduct.php b/app/YetiForce/Shop/AbstractBaseProduct.php index f547457a5857..66043123fbcf 100644 --- a/app/YetiForce/Shop/AbstractBaseProduct.php +++ b/app/YetiForce/Shop/AbstractBaseProduct.php @@ -1,13 +1,14 @@ + * @author Klaudia Łozowska |
---|