Custom process handler not working

Hi, I’m trying to create a process handler to call from the frontend. The custom handler I made (in this location: extensions\defaultExt\modules\EVN_Participantes\Process\Service\RecordActions\CheckAdminAction.php) is an exact copy of UserACLHandler (located in core\backend\Process\LegacyHandler\ACL\UserACLHandler.php) except for a different PROCESS_TYPE. When I try to call it from frontend i get a generic error from backend but when i use UserACLHandler instead it works fine. I think the system is not registering the new handler but it should be doing so with just implementing ProcessHandlerInterface. Anyone knows what i’m doing wrong?

Please check if the filename and the class name is matching. The class in extensions\defaultExt\modules\EVN_Participantes\Process\Service\RecordActions\CheckAdminAction.php should the name CheckAdminAction.

Yes it’s the same. Made sure to check twice.

Have you clear the cache?
Do you using debugger like Xdebug?

Yes I’ve cleared the cache and no I don’t use any debugger.

When you add new folders or files, you need to set correct file permissions.

You should use debugger to check errors like those, but you can set APP_ENV=hq in .env.local to show php error messages on response.

I’m on Windows, permissions should not be an issue.

I’ll see what i can do

If I am not wrong as per the compatibility matrix, we should not run SuiteCRM on the Windows OS.

I think people with Windows system usually install VirtualBox to run SuiteCRM v8.x :upside_down_face:

That hasn’t been an issue so far, I have even made some of the ‘add button’ tutorials from the youtube channel and those process handlers work fine.

Can you please check if you are able to run process handler that exist for accounts or contacts. I guess the PROCESS_TYPE string value for record actions should start ‘record-’.

Could you please share your code here? So, we can help you better.

1 Like
<?php

namespace App\Extension\defaultExt\modules\EVN_Participantes\Process\Service;

use ApiPlatform\Exception\InvalidArgumentException;
use App\Engine\LegacyHandler\LegacyHandler;
use App\Engine\LegacyHandler\LegacyScopeState;
use App\Module\Service\ModuleNameMapperInterface;
use App\Process\Entity\Process;
use App\Process\Service\ActionNameMapperInterface;
use App\Process\Service\BaseActionDefinitionProviderInterface;
use App\Process\Service\LegacyActionResolverInterface;
use App\Process\Service\ProcessHandlerInterface;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use UserACLService;

class CheckAdminAction extends LegacyHandler implements ProcessHandlerInterface, LoggerAwareInterface
{
    protected const MSG_OPTIONS_NOT_FOUND = 'Process options is not defined';
    protected const PROCESS_TYPE = 'check-admin';

    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * @var ModuleNameMapperInterface
     */
    private $moduleNameMapper;

    /**
     * @var BaseActionDefinitionProviderInterface
     */
    private $baseActionDefinitionProvider;

    /**
     * @var LegacyActionResolverInterface
     */
    private $legacyActionResolver;

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

    /**
     * @var ActionNameMapperInterface
     */
    private $actionNameMapper;

    /**
     * UserACLHandler constructor.
     * @param string $projectDir
     * @param string $legacyDir
     * @param string $legacySessionName
     * @param string $defaultSessionName
     * @param LegacyScopeState $legacyScopeState
     * @param RequestStack $session
     * @param ModuleNameMapperInterface $moduleNameMapper
     * @param BaseActionDefinitionProviderInterface $baseActionDefinitionProvider
     * @param LegacyActionResolverInterface $legacyActionResolver
     * @param array $adminOnlyModuleActions
     */
    public function __construct(
        string $projectDir,
        string $legacyDir,
        string $legacySessionName,
        string $defaultSessionName,
        LegacyScopeState $legacyScopeState,
        RequestStack $session,
        ModuleNameMapperInterface $moduleNameMapper,
        BaseActionDefinitionProviderInterface $baseActionDefinitionProvider,
        LegacyActionResolverInterface $legacyActionResolver,
        ActionNameMapperInterface $actionNameMapper,
        array $adminOnlyModuleActions
    ) {
        parent::__construct(
            $projectDir,
            $legacyDir,
            $legacySessionName,
            $defaultSessionName,
            $legacyScopeState,
            $session
        );
        $this->moduleNameMapper = $moduleNameMapper;
        $this->baseActionDefinitionProvider = $baseActionDefinitionProvider;
        $this->legacyActionResolver = $legacyActionResolver;
        $this->actionNameMapper = $actionNameMapper;
        $this->adminOnlyModuleActions = $adminOnlyModuleActions;
    }

    /**
     * @inheritDoc
     */
    public function getHandlerKey(): string
    {
        return self::PROCESS_TYPE;
    }

    /**
     * @inheritDoc
     */
    public function getProcessType(): string
    {
        return self::PROCESS_TYPE;
    }

    /**
     * @inheritDoc
     */
    public function requiredAuthRole(): string
    {
        return 'ROLE_USER';
    }

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

    /**
     * @inheritDoc
     */
    public function configure(
        Process $process
    ): void {
        //This process is synchronous
        //We aren't going to store a record on db
        //thus we will use process type as the id
        $process->setId(self::PROCESS_TYPE);
        $process->setAsync(false);
    }

    /**
     * @inheritDoc
     */
    public function validate(
        Process $process
    ): void {
        if (empty($process->getOptions())) {
            throw new InvalidArgumentException(self::MSG_OPTIONS_NOT_FOUND);
        }

        $options = $process->getOptions();
        [
            'module' => $module
        ] = $options;

        if (empty($module)) {
            throw new InvalidArgumentException(self::MSG_OPTIONS_NOT_FOUND);
        }
    }

    /**
     * @inheritDoc
     */
    public function run(Process $process)
    {
        $this->init();
        $this->startLegacyApp();

        /* @noinspection PhpIncludeInspection */
        require_once 'include/portability/Services/ACL/UserACLService.php';

        $options = $process->getOptions();
        [
            'module' => $module,
            'payload' => $payload
        ] = $options;

        $routeAction = $payload['routeAction'] ?? 'index';
        $routeRecord = $payload['record'] ?? '';
        $queryParams = $payload['queryParams'];

        $context = [
            'record' => $routeRecord
        ];

        $actionKey = $routeAction;

        $resolvedLegacyModule = $this->getResolvedLegacyModule($module, $actionKey, $queryParams);
        if (!empty($resolvedLegacyModule)) {
            $actionKey = $module;
            $module = $resolvedLegacyModule;
        }

        $legacyModuleName = $this->moduleNameMapper->toLegacy($module);
        $frontEndModuleName = $this->moduleNameMapper->toFrontEnd($module);

        $hasAccess = false;
        if ($this->moduleNameMapper->isValidModule($legacyModuleName)
            && ($this->baseActionDefinitionProvider->isActionAccessible($frontEndModuleName, $actionKey, $context))
        ) {
            $hasAccess = true;
        }

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

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

        $service = new UserACLService();
        $result = $service->run($legacyModuleName, $payload['routeURL'], $hasAccess);

        $process->setStatus('success');

        if ($result['status'] !== true) {
            $process->setStatus('error');
        }

        if (!empty($result['message'])) {
            $process->setMessages([
                $result['message']
            ]);
        }

        $this->close();

        $process->setData(['result' => $result['status']]);
    }

    /**
     * Get list of modules the user has access to
     * @param string $primaryAction
     * @param string $secondaryAction
     * @return string
     * @description Special case, when action is treated as module name by legacy
     * e.g. merge-records is an action but treated as a module by legacy
     * we need to identify the actual module(parent) name and the applicable acls in this case
     */
    protected function entryExistsInLegacyActionMapper(string $primaryAction, string $secondaryAction): string
    {
        $actionModuleIdentifierKey = $this->legacyActionResolver->get($primaryAction, $secondaryAction);

        if (empty($actionModuleIdentifierKey)) {
            return '';
        }

        return $actionModuleIdentifierKey;
    }

    /**
     * Get list of modules the user has access to
     * @param string $primaryAction
     * @param string $secondaryAction
     * @param array $queryParams
     * @return string
     */
    protected function getResolvedLegacyModule(
        string $primaryAction,
        string $secondaryAction,
        array $queryParams
    ): string {
        $actionModuleIdentifierKey = $this->entryExistsInLegacyActionMapper($primaryAction, $secondaryAction);

        if (empty($actionModuleIdentifierKey)) {
            return '';
        }

        return $queryParams[$actionModuleIdentifierKey] ?? '';
    }

    /**
     * @inheritDoc
     */
    public function setLogger(LoggerInterface $logger): void
    {
        $this->logger = $logger;
    }

    /**
     * @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;
    }
}

The file extensions\defaultExt\modules\EVN_Participantes\Process\Service\CheckAdminAction.php

What’s that process handler? not sure which one you’re reffering to. Also it’s not a record action, and i tried taking the file out of the RecordActions folder but still doesn’t work.

Can you please try with this ‘run’ method, I guess it is not ‘result’ but ‘value’.

public function run(Process $process)
    {

		$process->setStatus('success');
        $process->setMessages([]);
		$hasAccess=true;
        $process->setData(['value' => $hasAccess]);
    }

If above is working then you can learn that there may be some issue in the run method code. It is not a good idea, but you can try with hard codes values and get there to get the problem.