CVE-2023-3293 (PHP, published 2023-06-16T11:15Z)
Description: value: Cross-site Scripting (XSS) - Stored in GitHub repository salesagility/suitecrm-core prior to 8.3.0.
Severity: nan (CVSS v3 base score: 4.8)
Weakness: CWE-79 \u2014 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
Repository: https://github.com/salesagility/suitecrm-core
Files changed:
.env
README.md
VERSION
config/core_services.yaml
config/packages/security.php
config/services/system/admin_only_module_actions.yaml
core/app/common/package.json
core/app/core/package.json
core/app/core/src/lib/store/record-list/record-list.store.ts
core/app/core/src/lib/views/login/components/login/login.component.ts
core/backend/Engine/LegacyHandler/AclHandler.php
core/backend/Install/Service/Installation/InstallActionHandler.php
core/backend/Languages/LegacyHandler/SetSessionLanguage.php
core/backend/Migrations/Version20230420135520.php
core/backend/Module/LegacyHandler/Favorites/UpdateFavorite.php
core/backend/Module/LegacyHandler/RecentlyViewed/AddRecentlyViewed.php
core/backend/Module/LegacyHandler/RecentlyViewed/RecentlyViewedHandler.php
core/backend/Process/DataPersister/ProcessDataPersister.php
core/backend/Process/LegacyHandler/ACL/UserACLHandler.php
core/backend/Process/LegacyHandler/AddContactsToTargetListBulkActionHandler.php
core/backend/Process/LegacyHandler/AddRecordsToTargetListBulkActionHandler.php
core/backend/Process/LegacyHandler/LinkRelationHandler.php
core/backend/Process/LegacyHandler/ResetPasswordHandler.php
core/backend/Process/LegacyHandler/UnlinkRelationHandler.php
core/backend/Process/Service/BulkActions/CsvExportBulkAction.php
core/backend/Process/Service/BulkActions/DeleteRecordsBulkAction.php
core/backend/Process/Service/BulkActions/MassUpdateBulkAction.php
core/backend/Process/Service/BulkActions/MergeRecordsBulkAction.php
core/backend/Process/Service/BulkActions/PrintAsPdfBulkAction.php
core/backend/Process/Service/ProcessHandlerInterface.php
core/backend/Process/Service/RecordActions/ChangeLogAction.php
core/backend/Process/Service/RecordActions/ConvertLeadAction.php
core/backend/Process/Service/RecordActions/DeleteRecordAction.php
core/backend/Process/Service/RecordActions/DuplicateRecordAction.php
core/backend/Process/Service/RecordActions/EditAction.php
core/backend/Process/Service/RecordActions/MergeDuplicateAction.php
core/backend/Process/Service/RecordActions/PrintAsPdfAction.php
core/backend/Process/Service/RecordThreadItemActions/DeleteRecordThreadItemAction.php
core/backend/Process/Service/RecordThreadItemActions/DismissAlertAction.php
core/backend/Process/Service/RecordThreadItemActions/SnoozeRecordThreadItemAction.php
core/backend/Process/Service/RecordThreadListActions/DeleteAllRecordThreadListAction.php
core/backend/Process/Service/RecordThreadListActions/DismissAllAlertsRecordThreadListAction.php
core/backend/Process/Service/RecordThreadListActions/MarkAsReadRecordThreadListAction.php
core/backend/UserPreferences/LegacyHandler/SaveUiPreferences.php
package.json
public/legacy/data/SugarBean.php
public/legacy/include/MVC/SugarApplication.php
public/legacy/include/Smarty/plugins/function.sugarvar.php
public/legacy/include/Smarty/plugins/modifier.htmlpurify.php
public/legacy/include/SugarFields/Fields/Text/SugarFieldText.php
public/legacy/include/modules.php
public/legacy/include/portability/ApiBeanMapper/ApiBeanMapper.php
public/legacy/include/portability/ApiBeanMapper/TypeMappers/HtmlMapper.php
public/legacy/include/portability/ApiBeanMapper/TypeMappers/TextMapper.php
public/legacy/include/portability/ListView/ListViewDataPort.php
public/legacy/include/portability/Services/Relationships/AddContactsToTargetListService.php
public/legacy/include/utils.php
public/legacy/modules/SugarFeed/Dashlets/SugarFeedDashlet/SugarFeedDashlet.php
Commit message: SuiteCRM 8.3.0 Release


Vulnerable code:
# SuiteCRM 8.3-beta
8.3-beta
    $maxAttempts = (int)($env['LOGIN_THROTTLING_MAX_ATTEMPTS'] ?? 3);
                        'max_attempts' => $maxAttempts
                        'max_attempts' => $maxAttempts
                        'max_attempts' => $maxAttempts,
                        'max_attempts' => $maxAttempts,
  "version": "8.3-beta",
  "version": "8.3-beta",
        let size = 0;
        if (errorMessage === 'Too many failed login attempts, please try again in 1 minute.') {
use App\Process\Service\ProcessHandlerRegistry;
    public function __construct(ProcessHandlerRegistry $registry, Security $security)
use App\Engine\Service\AclManagerInterface;
     * @var AclManagerInterface
    private $acl;
     * @param AclManagerInterface $acl
        AclManagerInterface $acl
        $this->acl = $acl;
    protected function getResolvedLegacyModule(string $primaryAction, string $secondaryAction, array $queryParams): string
    {
use App\Process\Entity\Process;
        } catch (BadFunctionCallException | \InvalidArgumentException $e) {
use App\Process\Entity\Process;
use App\Process\Entity\Process;
use App\Process\Entity\Process;
use App\Process\Entity\Process;
  "version": "8.3-beta",
        if (!is_admin($GLOBALS['current_user']) && !empty($GLOBALS['adminOnlyList'][$this->controller->module]) && !empty($GLOBALS['adminOnlyList'][$this->controller->module]['all']) && (empty($GLOBALS['adminOnlyList'][$this->controller->module][$this->controller->action]) || $GLOBALS['adminOnlyList'][$this->controller->module][$this->controller->action] != 'allow')) {
    
        
r51719 - 2009-10-22 10:18:00 -0700 (Thu, 22 Oct 2009) - mitani - Converted to Build 3  tags and updated the build system 
r22124 - 2007-04-20 16:54:53 -0700 (Fri, 20 Apr 2007) - clee - 
 * @author Wayne Pan {wayne at sugarcrm.com}
	if(empty($params['key']))  {
	    $smarty->trigger_error("sugarvar: missing 'key' parameter");
	    return;
	}

	$object = (empty($params['objectName']))?$smarty->get_template_vars('parentFieldArray'): $params['objectName'];
	$displayParams = $smarty->get_template_vars('displayParams');


	if(empty($params['memberName'])){
		$member = $smarty->get_template_vars('vardef');
		$member = $member['name'];
	}else{
		$members = explode('.', $params['memberName']);
		$member =  $smarty->get_template_vars($members[0]);
		for($i = 1; $i < count($members); $i++){
			$member = $member[$members[$i]];
		}
	}

    $_contents =  '$'. $object . '.' . $member . '.' . $params['key'];
	if(empty($params['stringFormat']) && empty($params['string'])) {
		$_contents = '{' . $_contents;
		if(!empty($displayParams['htmlescape'])){
			$_contents .= '|escape:\'html\'';
		}
		if(!empty($params['htmlentitydecode'])){
			$_contents .= '|escape:\'html_entity_decode\'';
		}
		if(!empty($displayParams['strip_tags'])){
			$_contents .= '|strip_tags';
		}
		if(!empty($displayParams['url2html'])){
			$_contents .= '|url2html';
		}
		if(!empty($displayParams['nl2br'])){
			$_contents .= '|nl2br';
		}

		$_contents .= '}';
            if ($_REQUEST['action'] == "EditView") {
    //'Administration' => ['all'=>1, 'SupportPortal'=>'allow'],
        $endOffset = (floor(($totalCount - 1) / $limit)) * $limit;
function isSmtp($value): bool {
                          INNER JOIN securitygroups_users secu 
                            ON secg.id = secu.securitygroup_id 
                               AND secu.deleted = 0 
                          INNER JOIN securitygroups_records secr 
                            ON secg.id = secr.securitygroup_id 
                               AND secr.deleted = 0 
        /* END - SECURITY GROUPS */ 
        $listview = preg_replace('/\[(\w+)\:([\w\-\d]*)\:([^\]]*)\]/', '<a href="index.php?module=$1&action=DetailView&record=$2"><img src="themes/default/images/$1.gif" border=0 REPLACE_ALT>$3</a>', $listview); /*SKIP_IMAGE_TAG*/
        

Fixed code:

###> login throttling ###
LOGIN_THROTTLING_IP_LOGIN_MAX_ATTEMPTS=50
LOGIN_THROTTLING_INTERVAL="30 minutes"
###< login throttling ###
# SuiteCRM 8.3.0
8.3.0
      $adminOnlyModuleActions: '%system.admin_only_module_actions%'
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Security\Http\RateLimiter\DefaultLoginRateLimiter;
    $maxAttempts = (int)($env['LOGIN_THROTTLING_MAX_ATTEMPTS'] ?? 5);
    $ipLoginMaxAttempts = (int)($env['LOGIN_THROTTLING_IP_LOGIN_MAX_ATTEMPTS'] ?? 50);
    $loginThrottlingInterval = (string)($env['LOGIN_THROTTLING_INTERVAL'] ?? '30 minutes');

    $containerConfig->extension('framework', [
        'rate_limiter' => [
            // define 2 rate limiters (one for username+IP, the other for IP)
            'username_ip_login' => [
                'policy' => 'token_bucket',
                'limit' => $maxAttempts,
                'rate' => [ 'interval' => $loginThrottlingInterval ],
            ],
            'ip_login' => [
                'policy' => 'sliding_window',
                'limit' => $ipLoginMaxAttempts,
                'interval' => $loginThrottlingInterval,
            ],
        ],
    ]);

    $containerConfig->services()->set('app.login_rate_limiter')
        ->class(DefaultLoginRateLimiter::class)
        ->args(
            [
                // 1st argument is the limiter for IP
                new Reference('limiter.ip_login'),
                // 2nd argument is the limiter for username+IP
                new Reference('limiter.username_ip_login'),
            ]
        );
                        'limiter' => 'app.login_rate_limiter'
                        'limiter' => 'app.login_rate_limiter',
                        'limiter' => 'app.login_rate_limiter',
                        'limiter' => 'app.login_rate_limiter',
parameters:
  system.admin_only_module_actions:
    administration:
      '*': true
    users:
      'list': true
      'index': true
    'sugar-feed':
      'adminsettings': true
    'address-cache':
      '*': true
    maps:
      'geocoded_counts': true
      'geocoding_test': true
      'config': true
    external-oauth-connection:
      '*': true
  "version": "8.3.0",
  "version": "8.3.0",
        let size = 20;
        if (typeof errorMessage === 'string' && errorMessage.includes('Too many failed login attempts, please try again in')) {
        if (check_default_module_access($legacyName) === false) {
            $hasAccess = false;
        }

    /**
     * @inheritDoc
     */
    public function getRequiredACLs(Process $process): array
    {
        return [];
    }

    /**
     * @inheritDoc
     */
    public function getRequiredACLs(Process $process): array
    {
        return [];
    }

<?php
/**
 * SuiteCRM is a customer relationship management program developed by SalesAgility Ltd.
 * Copyright (C) 2023 SalesAgility Ltd.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by the
 * Free Software Foundation with the addition of the following permission added
 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
 * IN WHICH THE COPYRIGHT IS OWNED BY SALESAGILITY, SALESAGILITY DISCLAIMS THE
 * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "Supercharged by SuiteCRM" logo. If the display of the logos is not reasonably
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Supercharged by SuiteCRM".
 */

declare(strict_types=1);

namespace App\Migrations;

use Doctrine\DBAL\Schema\Schema;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

final class Version20230420135520 extends BaseMigration implements ContainerAwareInterface
{
    use EnvHandlingMigrationTrait;

    /**
     * @var ContainerInterface
     */
    protected $container;

    /**
     * @var LoggerInterface
     */
    protected $upgradeLogger;

    public function getDescription(): string
    {
        return 'Add login throttling interval to .env';
    }

    public function isTransactional(): bool
    {
        return false;
    }

    public function up(Schema $schema): void
    {
        $envFile = $this->getProjectDir() . "/.env";

        if (!file_exists($envFile)) {
            return;
        }

        $envContents = file_get_contents($envFile);

        $this->addLoginThrottlingConfig($envContents, $envFile);

    }

    public function down(Schema $schema): void
    {
    }

    /**
     * Check and add missing login throttling config
     * @param $envContents
     * @param string $envFile
     */
    protected function addLoginThrottlingConfig(&$envContents, string $envFile): void
    {
        $properties = [
            'LOGIN_THROTTLING_IP_LOGIN_MAX_ATTEMPTS' => '50',
            'LOGIN_THROTTLING_INTERVAL' => '"30 minutes"'
        ];

        $wrapperStart = '###> login throttling ###';
        $wrapperEnd = '###< login throttling ###';

        $propertiesToAdd = $this->getContentToAdd($envContents, $properties, $wrapperStart, $wrapperEnd);
        if (!empty($propertiesToAdd)) {
            $envContents .= $propertiesToAdd;
            file_put_contents($envFile, $envContents);
            $this->log('Added LOGIN_THROTTLING_INTERVAL to .env.');

            return;
        }

        $this->log('LOGIN_THROTTLING_INTERVAL already in .env, skipping.');
    }
}
    /**
     * @inheritDoc
     */
    public function getRequiredACLs(Process $process): array
    {
        ['favorite' => $favorite] = $process->getOptions();

        $module = $favorite['attributes']['parent_type'] ?? '';
        $id = $favorite['attributes']['parent_id'] ?? '';

        return [
            $module => [
                [
                    'action' => 'view',
                    'record' => $id
                ]
            ]
        ];
    }


        ['favorite' => $favorite, 'action' => $action] = $process->getOptions();

        $module = $favorite['attributes']['parent_type'] ?? '';
        $id = $favorite['attributes']['parent_id'] ?? '';

        if (empty($module) || empty($id) || empty($action)) {
            throw new InvalidArgumentException(self::MSG_OPTIONS_NOT_FOUND);
        }
    /**
     * @inheritDoc
     */
    public function getRequiredACLs(Process $process): array
    {
        ['recentlyViewed' => $recentlyViewed] = $process->getOptions();
        $itemId = $recentlyViewed['attributes']['item_id'] ?? '';
        $itemModule = $recentlyViewed['attributes']['module_name'] ?? '';

        return [
            $itemModule => [
                [
                    'action' => 'view',
                    'record' => $itemId,
                ]
            ]
        ];
    }


        ['recentlyViewed' => $recentlyViewed] = $process->getOptions();
        $itemModule = $recentlyViewed['attributes']['module_name'] ?? '';
        $itemModule = $this->moduleNameMapper->toLegacy($itemModule);
        $action = $recentlyViewed['attributes']['action'] ?? '';


        if (empty($itemModule) || empty($action)) {
            throw new InvalidArgumentException(self::MSG_OPTIONS_NOT_FOUND);
        }
/**
 * SuiteCRM is a customer relationship management program developed by SalesAgility Ltd.
 * Copyright (C) 2021 SalesAgility Ltd.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by the
 * Free Software Foundation with the addition of the following permission added
 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
 * IN WHICH THE COPYRIGHT IS OWNED BY SALESAGILITY, SALESAGILITY DISCLAIMS THE
 * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "Supercharged by SuiteCRM" logo. If the display of the logos is not reasonably
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Supercharged by SuiteCRM".
 */
use App\Engine\Service\AclManagerInterface;
use App\Process\Service\ProcessHandlerRegistry;

    /**
     * @var AclManagerInterface
     */
    private $acl;

     * @param AclManagerInterface $acl
    public function __construct(ProcessHandlerRegistry $registry, Security $security, AclManagerInterface $acl)
        $this->acl = $acl;

        $hasAccess = $this->checkACLAccess($processHandler, $process);

        if (!$hasAccess) {
            $process->setMessages(['LBL_ACCESS_DENIED']);
            $process->setStatus('error');

            return $process;
        }

    /**
     * Check acl access
     * @param ProcessHandlerInterface $processHandler
     * @param Process $process
     */
    protected function checkACLAccess(ProcessHandlerInterface $processHandler, Process $process): bool
    {
        $modulesACLs = $processHandler->getRequiredACLs($process) ?? [];
        if (empty($modulesACLs)) {
            return true;
        }

        $hasAccess = true;
        foreach ($modulesACLs as $module => $requiredACLs) {
            if (empty($requiredACLs)) {
                continue;
            }
            if (empty($module)) {
                continue;
            }

            if ($hasAccess === false) {
                return false;
            }

            foreach ($requiredACLs as $requiredACL) {
                $record = $requiredACL['record'] ?? '';
                $ids = $requiredACL['ids'] ?? '';
                $action = $requiredACL['action'] ?? '';

                if (empty($action)) {
                    continue;
                }

                if ($hasAccess === false) {
                    return false;
                }

                if (empty($record) && empty($ids)) {
                    $hasAccess &= $this->acl->checkAccess($module, $action, true, 'module', true);
                    continue;
                }

                if (!empty($record)) {
                    $hasAccess &= $this->acl->checkRecordAccess($module, $action, $record);
                    continue;
                }

                if (!empty($ids)) {
                    $hasAccess &= $this->checkRecordsAccess($ids, $module, $action);
                }
            }
        }

        return $hasAccess;
    }


    /**
     * @param array $ids
     * @param string $module
     * @param string $action
     * @return bool
     */
    protected function checkRecordsAccess(array $ids, string $module, string $action): bool
    {
        if (empty($ids)) {
            return true;
        }

        foreach ($ids as $id) {
            $hasAccess = $this->acl->checkRecordAccess($module, $action, $id);
            if ($hasAccess === false) {
                return false;
            }
        }

        return true;
    }
use App\Process\Service\ActionNameMapperInterface;

    /**
     * @var array
     */
    private $adminOnlyModuleActions;

     * @var ActionNameMapperInterface
    private $actionNameMapper;
     * @param array $adminOnlyModuleActions
        ActionNameMapperInterface $actionNameMapper,
        array $adminOnlyModuleActions
        $this->actionNameMapper = $actionNameMapper;
        $this->adminOnlyModuleActions = $adminOnlyModuleActions;
    /**
     * @inheritDoc
     */
    public function getRequiredACLs(Process $process): array
    {
        return [];
    }

        global $current_user;
        $isAdmin = is_admin($current_user);
        $isActionAdminOnly = $this->isAdminOnlyAction($frontEndModuleName, $legacyModuleName, $actionKey, $queryParams);

        if ($isActionAdminOnly && !$isAdmin) {
            $hasAccess = false;
        }


    protected function getResolvedLegacyModule(
        string $primaryAction,
        string $secondaryAction,
        array $queryParams
    ): string {

    /**
     * @param string $module
     * @param string $legacyModule
     * @param string $actionKey
     * @param array $queryParams
     * @return bool
     */
    protected function isAdminOnlyAction(string $module, string $legacyModule,  string $actionKey, array $queryParams): bool
    {
        if (!empty($queryParams['import_module']) && strtolower($module) === 'import') {
            $module = $this->moduleNameMapper->toFrontEnd($queryParams['import_module']);
            $legacyModule = $queryParams['import_module'];
            $actionKey = 'import';
        }

        $adminOnlyList = [];

        /* @noinspection PhpIncludeInspection */
        require_once 'include/modules.php';
        $legacyActionName = strtolower($this->actionNameMapper->toLegacy($actionKey));
        $adminOnlyActions = $adminOnlyList[$legacyModule] ?? [];
        $adminOnlyAction = $adminOnlyActions[$legacyActionName] ?? $adminOnlyActions['all'] ?? false;
        $isActionAdminOnly = !empty($adminOnlyAction) && $adminOnlyAction !== 'allow';

        if ($isActionAdminOnly) {
            return true;
        }

        $adminOnlyActions = $this->adminOnlyModuleActions[$module] ?? [];

        return $adminOnlyActions[strtolower($actionKey)] ?? $adminOnlyActions['*'] ?? false;
    }
    /**
     * @inheritDoc
     */
    public function getRequiredACLs(Process $process): array
    {
        $options = $process->getOptions();
        $baseModule = $options['module'] ?? '';
        $baseIds = $options['ids'] ?? [];
        $modalRecord = $options['modalRecord'] ?? [];
        $modalModule = $modalRecord['module'] ?? '';
        $modalRecordId = $modalRecord['id'] ?? '';

        return [
            $baseModule => [
                [
                    'action' => 'view',
                    'ids' => $baseIds
                ]
            ],
            $modalModule => [
                [
                    'action' => 'view',
                    'record' => $modalRecordId,
                ]
            ],
        ];
    }

        $this->startLegacyApp();
    /**
     * @inheritDoc
     */
    public function getRequiredACLs(Process $process): array
    {
        $options = $process->getOptions();
        $baseModule = $options['module'] ?? '';
        $baseIds = $options['ids'] ?? [];
        $modalRecord = $options['modalRecord'] ?? [];
        $modalModule = $modalRecord['module'] ?? '';

        return [
            $baseModule => [
                [
                    'action' => 'view',
                    'ids' => $baseIds
                ]
            ],
            $modalModule => [
                ['action' => 'view']
            ],
        ];
    }

    /**
     * @inheritDoc
     */
    public function getRequiredACLs(Process $process): array
    {
        $options = $process->getOptions();
        $baseModule = $options['module'] ?? '';
        $baseModuleId = $options['id'] ?? '';
        $payload = $options['payload'] ?? [];
        $relateModule = $payload['relateModule'] ?? '';
        $relateRecordIds = $payload['relateRecordIds'] ?? [];

        return [
            $baseModule => [
                [
                    'action' => 'view',
                    'record' => $baseModuleId
                ]
            ],
            $relateModule => [
                [
                    'action' => 'view',
                    'ids' => $relateRecordIds
                ]
            ]
        ];
    }

use App\Process\Entity\Process;
    /**
     * @inheritDoc
     */
    public function getRequiredACLs(Process $process): array
    {
        return [];
    }


        } catch (BadFunctionCallException|\InvalidArgumentException $e) {
    /**
     * @inheritDoc
     */
    public function getRequiredACLs(Process $process): array
    {
        $options = $process->getOptions();
        $payload = $options['payload'] ?? [];
        $baseModule = $payload['baseModule'] ?? '';
        $relateModule = $payload['relateModule'] ?? '';
        $relateModuleId = $payload['relateRecordId'] ?? '';

        $acls = [
            $baseModule => [
                [
                    'action' => 'view',
                    'record' => $payload['baseRecordId'] ?? ''
                ],
                [
                    'action' => 'edit'
                ],
            ],
            $relateModule => [
                [
                    'action' => 'view',
                    'record' => $relateModuleId
                ]
            ]
        ];

        if (!empty($relateModule) && !empty($relateModuleId)) {
            $acls[$relateModule] = [
                [
                    'action' => 'view',
                    'record' =>  $relateModuleId ?? ''
                ]
            ];
        }

        return $acls;
    }


use App\Process\Entity\Process;
    /**
     * @inheritDoc
     */
    public function getRequiredACLs(Process $process): array
    {
        $options = $process->getOptions();
        $module = $options['module'] ?? '';
        $ids = $options['ids'] ?? [];

        return [
            $module => [
                [
                    'action' => 'export',
                    'ids' => $ids
                ]
            ]
        ];
    }

    /**
     * @inheritDoc
     */
    public function getRequiredACLs(Process $process): array
    {
        $options = $process->getOptions();
        $module = $options['module'] ?? '';
        $ids = $options['ids'] ?? [];

        return [
            $module => [
                [
                    'action' => 'delete',
                    'ids' => $ids
                ]
            ]
        ];
    }

    /**
     * @inheritDoc
     */
    public function getRequiredACLs(Process $process): array
    {
        $options = $process->getOptions();
        $module = $options['module'] ?? '';
        $ids = $options['ids'] ?? [];

        return [
            $module => [
                [
                    'action' => 'edit',
                    'ids' => $ids
                ],
                [
                    'action' => 'massupdate'
                ]
            ],
        ];
    }


use App\Process\Entity\Process;
    /**
     * @inheritDoc
     */
    public function getRequiredACLs(Process $process): array
    {
        $options = $process->getOptions();
        $module = $options['module'] ?? '';
        $ids = $options['ids'] ?? [];

        return [
            $module => [
                [
                    'action' => 'view'
                ],
                [
                    'action' => 'edit',
                    'ids' => $ids
                ]
            ]
        ];
    }

use App\Process\Entity\Process;
    /**
     * @inheritDoc
     */
    public function getRequiredACLs(Process $process): array
    {
        $options = $process->getOptions();
        $module = $options['module'] ?? '';
        $ids = $options['ids'] ?? [];


        return [
            $module => [
                [
                    'action' => 'view'
                ],
                [
                    'action' => 'export',
                    'ids' => $ids
                ]
            ]
        ];
    }

    /**
     * Get required acls list per module, empty array means no acl check needed
     *
     * @param Process $process
     * @return array
     */
    public function getRequiredACLs(Process $process): array;

    /**
     * @inheritDoc
     */
    public function getRequiredACLs(Process $process): array
    {
        $options = $process->getOptions();
        $module = $options['module'] ?? '';
        $recordId = $options['id'] ?? '';

        return [
            $module => [
                [
                    'action' => 'view',
                    'record' => $recordId
                ]
            ],
        ];
    }

use App\Process\Entity\Process;
    /**
     * @inheritDoc
     */
    public function getRequiredACLs(Process $process): array
    {
        $options = $process->getOptions();
        $module = $options['module'] ?? '';
        $id = $options['id'] ?? '';

        return [
            $module => [
                [
                    'action' => 'view',
                    'record' => $id
                ],
            ],
        ];

    }

    /**
     * @inheritDoc
     */
    public function getRequiredACLs(Process $process): array
    {
        $options = $process->getOptions();
        $module = $options['module'] ?? '';

        return [
            $module => [
                [
                    'action' => 'delete',
                    'record' => $options['id'] ?? ''
                ]
            ],
        ];
    }

    /**
     * @inheritDoc
     */
    public function getRequiredACLs(Process $process): array
    {
        $options = $process->getOptions();
        $module = $options['module'] ?? '';

        return [
            $module => [
                [
                    'action' => 'edit',
                    'record' => $options['id'] ?? ''
                ]
            ],
        ];
    }

    /**
     * @inheritDoc
     */
    public function getRequiredACLs(Process $process): array
    {
        $options = $process->getOptions();
        $module = $options['module'] ?? '';

        $baseModule = $options['payload']['baseModule'] ?? '';
        $baseRecord = $options['payload']['baseRecordId'] ?? '';

        return [
            $module => [
                [
                    'action' => 'edit',
                    'record' => $options['id'] ?? ''
                ]
            ],
            $baseModule => [
                [
                    'action' => 'view',
                    'record' => $baseRecord
                ]
            ],
        ];
    }

    /**
     * @inheritDoc
     */
    public function getRequiredACLs(Process $process): array
    {
        $options = $process->getOptions();
        $module = $options['module'] ?? '';

        return [
            $module => [
                [
                    'action' => 'edit',
                    'record' => $options['id'] ?? ''
                ]
            ],
        ];
    }

    /**
     * @inheritDoc
     */
    public function getRequiredACLs(Process $process): array
    {
        $options = $process->getOptions();
        $module = $options['module'] ?? '';

        $modalRecord = $options['params']['modalRecord'] ?? [];
        $modalRecordModule = $modalRecord['module'] ?? '';
        $modalRecordId = $modalRecord['id'] ?? '';

        $acls = [
            $module => [
                [
                    'action' => 'view',
                    'record' => $options['id'] ?? ''
                ],
                [
                    'action' => 'export',
                    'record' => $options['id'] ?? ''
                ]
            ],
        ];

        if ($modalRecordModule !== '') {
            $acls[$modalRecordModule] = [
                [
                    [
                        'action' => 'view',
                        'record' => $modalRecordId
                    ]
                ]
            ];
        }

        return $acls;

    }

    /**
     * @inheritDoc
     */
    public function getRequiredACLs(Process $process): array
    {
        $options = $process->getOptions();
        $module = $options['module'] ?? '';

        return [
            $module => [
                [
                    'action' => 'delete',
                    'record' => $options['id'] ?? ''
                ]
            ],
        ];
    }

    /**
     * @inheritDoc
     */
    public function getRequiredACLs(Process $process): array
    {
        $options = $process->getOptions();
        $module = $options['module'] ?? '';

        return [
            $module => [
                [
                    'action' => 'delete',
                    'record' => $options['id'] ?? ''
                ]
            ],
        ];
    }

    /**
     * @inheritDoc
     */
    public function getRequiredACLs(Process $process): array
    {
        $options = $process->getOptions();
        $module = $options['module'] ?? '';

        return [
            $module => [
                [
                    'action' => 'view',
                    'record' => $options['id'] ?? ''
                ]
            ],
        ];
    }

    /**
     * @inheritDoc
     */
    public function getRequiredACLs(Process $process): array
    {
        $options = $process->getOptions();
        $module = $options['module'] ?? '';
        $ids = $options['ids'] ?? [];

        return [
            $module => [
                [
                    'action' => 'delete',
                    'ids' => $ids
                ]
            ],
        ];
    }

    /**
     * @inheritDoc
     */
    public function getRequiredACLs(Process $process): array
    {
        $options = $process->getOptions();
        $ids = $options['ids'] ?? [];

        return [
            'alerts' => [
                [
                    'action' => 'delete',
                    'ids' => $ids
                ]
            ],
        ];
    }

    /**
     * @inheritDoc
     */
    public function getRequiredACLs(Process $process): array
    {
        $options = $process->getOptions();
        $module = $options['module'] ?? '';
        $ids = $options['ids'] ?? [];

        return [
            $module => [
                [
                    'action' => 'view',
                    'ids' => $ids
                ]
            ],
        ];
    }

    /**
     * @inheritDoc
     */
    public function getRequiredACLs(Process $process): array
    {
        return [];
    }

  "version": "8.3.0",

        if (check_default_module_access($this->module_name ?? '') === false) {
            return false;
        }


        $module = $this->controller->module ?? '';
        $action = strtolower($this->controller->action ?? '');

        if (!empty($_REQUEST['import_module'] ?? '') && strtolower($module) === 'import') {
            $module = $_REQUEST['import_module'] ?? '';
            $action = 'import';
        }

        $adminOnlyList = $GLOBALS['adminOnlyList'] ?? [];
        $adminOnlyModuleActions = $adminOnlyList[$module] ?? [];
        $adminOnlyAction = $adminOnlyModuleActions[$action] ?? $adminOnlyModuleActions['all'] ?? false;
        $isAdminOnly = !empty($adminOnlyAction) && $adminOnlyAction !== 'allow';

        if ($isAdminOnly && !is_admin($GLOBALS['current_user'])) {


r51719 - 2009-10-22 10:18:00 -0700 (Thu, 22 Oct 2009) - mitani - Converted to Build 3  tags and updated the build system
r22124 - 2007-04-20 16:54:53 -0700 (Fri, 20 Apr 2007) - clee -
 * @author Wayne Pan {wayne at sugarcrm.com}
    if (empty($params['key'])) {
        $smarty->trigger_error("sugarvar: missing 'key' parameter");

        return;

    $object = (empty($params['objectName'])) ? $smarty->get_template_vars('parentFieldArray') : $params['objectName'];
    $displayParams = $smarty->get_template_vars('displayParams');


    if (empty($params['memberName'])) {
        $member = $smarty->get_template_vars('vardef');
        $member = $member['name'];
    } else {
        $members = explode('.', $params['memberName']);
        $member = $smarty->get_template_vars($members[0]);
        for ($i = 1; $i < count($members); $i++) {
            $member = $member[$members[$i]];
        }
    }

    $_contents = '$' . $object . '.' . $member . '.' . $params['key'];
    if (empty($params['stringFormat']) && empty($params['string'])) {
        $_contents = '{' . $_contents;
        if (!empty($displayParams['htmlescape'])) {
            $_contents .= '|escape:\'html\'';
        }
        if (!empty($params['htmlentitydecode'])) {
            $_contents .= '|escape:\'html_entity_decode\'';
        }
        if (!empty($displayParams['strip_tags'])) {
            $_contents .= '|strip_tags';
        }
        if (!empty($displayParams['url2html'])) {
            $_contents .= '|url2html';
        }
        if (!empty($displayParams['nl2br'])) {
            $_contents .= '|nl2br';
        }
        if (!empty($displayParams['purifyHtml'])) {
            $_contents .= '|htmlpurify';
        }

        $_contents .= '}';
    }


<?php
/**
 * SuiteCRM is a customer relationship management program developed by SalesAgility Ltd.
 * Copyright (C) 2023 SalesAgility Ltd.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by the
 * Free Software Foundation with the addition of the following permission added
 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
 * IN WHICH THE COPYRIGHT IS OWNED BY SALESAGILITY, SALESAGILITY DISCLAIMS THE
 * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "Supercharged by SuiteCRM" logo. If the display of the logos is not reasonably
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Supercharged by SuiteCRM".
 */

/**
 * Smarty plugin
 * @package Smarty
 * @subpackage plugins
 */

/**
 * Smarty HTML purifier modifier plugin
 *
 * Type:     modifier<br>
 * Name:     htmlpurify<br>
 * Purpose:  purify html to display
 * @param string
 * @return string
 */

function smarty_modifier_htmlpurify($string)
{
    $string = purify_html(securexss($string));

    return $string;
}
        if (isset($vardef['editor']) && $vardef['editor'] === "html") {
            $displayParams['purifyHtml'] = $displayParams['purifyHtml'] ?? true;
        }

            if (isset($vardef['editor']) && $vardef['editor'] === "html") {
                $displayParams['purifyHtml'] = $displayParams['purifyHtml'] ?? true;
            }
            if ($_REQUEST['action'] === "EditView") {
    'Administration' => ['all'=>1, 'supportportal'=>'allow'],
    'ExternalOAuthConnection' => ['all' => 1],
    'jjwg_Address_Cache' => ['all' => 1],
    'SugarFeed' => ['adminsettings' => 1],
    'jjwg_Maps' => ['geocoded_counts' => 1, 'geocoding_test' => 1, 'config' => 1],
require_once __DIR__ . '/TypeMappers/HtmlMapper.php';
require_once __DIR__ . '/TypeMappers/TextMapper.php';
        $this->typeMappers[HtmlMapper::getType()] = new HtmlMapper();
        $this->typeMappers[TextMapper::getType()] = new TextMapper();
<?php
/**
 * SuiteCRM is a customer relationship management program developed by SalesAgility Ltd.
 * Copyright (C) 2023 SalesAgility Ltd.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by the
 * Free Software Foundation with the addition of the following permission added
 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
 * IN WHICH THE COPYRIGHT IS OWNED BY SALESAGILITY, SALESAGILITY DISCLAIMS THE
 * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see http://www.gnu.org/licenses.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "Supercharged by SuiteCRM" logo. If the display of the logos is not reasonably
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Supercharged by SuiteCRM".
 */

require_once __DIR__ . '/TypeMapperInterface.php';

class HtmlMapper implements TypeMapperInterface
{
    /**
     * @inheritDoc
     */
    public static function getType(): string
    {
        return 'html';
    }

    /**
     * @inheritDoc
     */
    public function toApi(SugarBean $bean, array &$container, string $name, string $alternativeName = ''): void
    {
        $newName = $name;

        if (!empty($alternativeName)) {
            $newName = $alternativeName;
        }

        if (empty($bean->$name)) {
            $container[$newName] = '';

            return;
        }

        $container[$newName] = $bean->$name;
    }

    /**
     * @inheritDoc
     */
    public function toBean(SugarBean $bean, array &$container, string $name, string $alternativeName = ''): void
    {
        $newName = $name;

        if (!empty($alternativeName)) {
            $newName = $alternativeName;
        }

        $fieldDef = $bean->field_defs[$newName] ?? [];

        $purifyHtml = $fieldDef['metadata']['purifyHtml'] ?? true;
        $value = $container[$newName] ?? '';

        if ($purifyHtml === false || empty($value) || !is_string($value)) {
            return;
        }

        $container[$newName] = purify_html(securexss($value));
    }
}
<?php
/**
 * SuiteCRM is a customer relationship management program developed by SalesAgility Ltd.
 * Copyright (C) 2023 SalesAgility Ltd.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by the
 * Free Software Foundation with the addition of the following permission added
 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
 * IN WHICH THE COPYRIGHT IS OWNED BY SALESAGILITY, SALESAGILITY DISCLAIMS THE
 * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see http://www.gnu.org/licenses.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "Supercharged by SuiteCRM" logo. If the display of the logos is not reasonably
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Supercharged by SuiteCRM".
 */

require_once __DIR__ . '/TypeMapperInterface.php';

class TextMapper implements TypeMapperInterface
{
    /**
     * @inheritDoc
     */
    public static function getType(): string
    {
        return 'text';
    }

    /**
     * @inheritDoc
     */
    public function toApi(SugarBean $bean, array &$container, string $name, string $alternativeName = ''): void
    {
        $newName = $name;

        if (!empty($alternativeName)) {
            $newName = $alternativeName;
        }

        if (empty($bean->$name)) {
            $container[$newName] = '';

            return;
        }

        $container[$newName] = $bean->$name;
    }

    /**
     * @inheritDoc
     */
    public function toBean(SugarBean $bean, array &$container, string $name, string $alternativeName = ''): void
    {
        $newName = $name;

        if (!empty($alternativeName)) {
            $newName = $alternativeName;
        }

        $fieldDef = $bean->field_defs[$newName] ?? [];

        $editor = $fieldDef['editor'] ?? '';
        $purifyHtml = $fieldDef['metadata']['purifyHtml'] ?? true;

        if (empty($editor) || $editor !== 'html' || $purifyHtml === false || !is_string($container[$newName] ?? '')) {
            return;
        }

        $container[$newName] = purify_html(securexss($container[$newName] ?? ''));
    }
}
        if ($limit > 0) {
            $endOffset = (floor(($totalCount - 1) / $limit)) * $limit;
        } else {
            $endOffset = 0;
        }


                if (empty($contact)) {
                    continue;
                }

                $hasAccess = $contact->ACLAccess('view');
                if (!$hasAccess) {
                    continue;
                }

        'default_module_access' => [
            'SecurityGroups' => false,
            'AOW_WorkFlow' => false
        ],
        'default_module_access' => [
            'SecurityGroups' => false,
            'AOW_WorkFlow' => false,
        ],
/**
 * Check if user has action acls defined
 * @param string $module
 * @param string $action
 * @return bool
 */
function has_action_acls_defined(string $module, string $action): bool
{
    global $current_user;

    $hasGroupActionAcls = true;

    $aclActions = ACLAction::getUserActions($current_user->id, false, $module, 'module', $action);
    $isDefaultListACL = !empty($aclActions['isDefault']) && isTrue($aclActions['isDefault']);

    if (empty($aclActions) || $isDefaultListACL) {
        $hasGroupActionAcls = false;
    }

    return $hasGroupActionAcls;
}

/**
 * Check default module access
 * @param string $module
 * @return bool
 */
function check_default_module_access(string $module): bool
{
    global $sugar_config, $current_user;

    if (empty($module) || is_admin($current_user)) {
        return true;
    }

    $hasActionAclsDefined = has_action_acls_defined($module, 'access');
    $defaultModuleAccessConfig = $sugar_config['default_module_access'] ?? [];
    $defaultModuleAccess = $defaultModuleAccessConfig[$module] ?? true;
    if ($defaultModuleAccess === false && isFalse($hasActionAclsDefined ?? false)) {
        return false;
    }

    return true;
}

function isSmtp($value): bool
{
                          INNER JOIN securitygroups_users secu
                            ON secg.id = secu.securitygroup_id
                               AND secu.deleted = 0
                          INNER JOIN securitygroups_records secr
                            ON secg.id = secr.securitygroup_id
                               AND secr.deleted = 0
        /* END - SECURITY GROUPS */
        require_once 'include/portability/RouteConverter.php';
        $routeConverter = new RouteConverter();

        $listview = preg_replace_callback(
            '/\[(\w+)\:([\w\-\d]*)\:([^\]]*)\]/',
            static function (&$matches) use ($routeConverter) {
                $module = $matches[1] ?? '';
                $record = $matches[2] ?? '';
                $name = $matches[3] ?? '';
                $uiLink = str_replace('./', '../', $routeConverter->convert($module, 'DetailView', $record, []));

                return '<a href="' . $uiLink . '">' . $name . '</a>';
            },
            $listview
        );/*SKIP_IMAGE_TAG*/