/home/ivoiecob/email.hirewise-va.com/system/Module/AbstractModule.php
<?php
/**
 * This code is licensed under AGPLv3 license or Afterlogic Software License
 * if commercial version of the product was purchased.
 * For full statements of the licenses see LICENSE-AFTERLOGIC and LICENSE-AGPL3 files.
 */

namespace Aurora\System\Module;

use Aurora\Api;
use Illuminate\Support\Str;

/**
 * @license https://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0
 * @license https://afterlogic.com/products/common-licensing Afterlogic Software License
 * @copyright Copyright (c) 2019, Afterlogic Corp.
 *
 * @package Api
 */
abstract class AbstractModule
{
    /**
     * @var string
     */
    protected $sPath;

    /**
     * @var string
     */
    protected $sVersion;

    /**
     * @var array
     */
    protected $aManagersCache = [];

    /**
     * @var array
     */
    protected $aParameters;

    /**
     * @var array
     */
    protected $aObjects = [];

    /**
     * @var \MailSo\Base\Http
     */
    public $oHttp;

    /**
     * @var array
     */
    protected $aConfig;

    /**
     *
     * @var \Aurora\System\Module\Settings
     */
    public $oModuleSettings = null;

    /**
     *
     * @var array
     */
    protected $aSettingsMap = [];

    /**
     *
     * @var string
     */
    public static $Delimiter = '::';

    /**
     *
     * @var bool
     */
    protected $bInitialized = false;

    /**
     *
     * @var array
     */
    protected $aRequireModules = [];

    /**
     *
     * @var array
     */
    protected $aSkipedEvents = [];

    /**
     *
     * @var array
     */
    public $aErrors = [];

    /**
     *
     * @var array
     */
    public $aAdditionalEntityFieldsToEdit = [];

    /**
     *
     * @var Manager
     */
    protected $oModuleManager = null;

    /**
     *
     * @var boolean
     */
    protected $bIsPermanent = false;

    protected $aDeniedMethodsByWebApi =  [
        '__construct',
        'init',
        'initialize',
        'createInstance',
        'getInstance',
        'Decorator',
        'RequireModule',
        'GetRequireModules',
        'isPermanent',
        'isValid',
        'getNamespace',
        'getModuleSettings',
        'loadModuleSettings',
        'saveModuleConfig',
        'getConfig',
        'setConfig',
        'denyMethodsCallByWebApi',
        'denyMethodCallByWebApi',
        'subscribeEvent',
        'broadcastEvent',
        'skipEvent',
        'removeEventFromSkiped',
        'includeTemplate',
        'extendObject',
        'getExtendedObject',
        'issetObject',
        'SetPath',
        'GetHash',
        'GetName',
        'GetPath',
        'GetVersion',
        'GetFullName',
        'AddEntry',
        'AddEntries',
        'HasEntry',
        'RemoveEntry',
        'RemoveEntries',
        'GetEntryCallback',
        'DefaultResponse',
        'TrueResponse',
        'FalseResponse',
        'ExceptionResponse',
        'CallMethod',
        'i18N',
        'GetErrors',
        'GetErrorMessageByCode',
        'GetAdditionalEntityFieldsToEdit',
    ];

    /**
     *
     * @var array
     */
    protected $aLang;

    /**
     * @param string $sVersion
     */
    public function __construct($sPath, $sVersion = '1.0')
    {
        $this->sVersion = (string) $sVersion;

        $this->sPath = $sPath . self::GetName();
        $this->aParameters = [];
        $this->oHttp = \MailSo\Base\Http::SingletonInstance();
        $this->oModuleManager = \Aurora\System\Api::GetModuleManager();
    }

    /**
     *
     * @return void
     */
    abstract public function init();

    /**
     *
     * @param string $sPath
     * @param string $sVersion
     * @return \Aurora\System\Module\AbstractModule
     */
    public static function createInstance($sPath, $sVersion = '1.0')
    {
        /* @phpstan-ignore-next-line */
        return new static($sPath, $sVersion);
    }

    /**
     *
     * @return \Aurora\System\Module\AbstractModule
     */
    public static function getInstance()
    {
        return \Aurora\System\Api::GetModule(self::GetName());
    }

    /**
     *
     * @return \Aurora\System\Module\Decorator
     */
    public static function Decorator()
    {
        return \Aurora\System\Api::GetModuleDecorator(self::GetName());
    }

    /**
     *
     * @param \Aurora\System\Module\Manager $oModuleManager
     * @return  \Aurora\System\Module\Manager
     */
    protected function SetModuleManager(Manager $oModuleManager)
    {
        return $this->oModuleManager = $oModuleManager;
    }

    /**
     *
     * @return \Aurora\System\Module\Manager
     */
    protected function GetModuleManager()
    {
        return $this->oModuleManager;
    }

    /**
     *
     * @param string $sModule
     */
    public function RequireModule($sModule)
    {
        if (!in_array($sModule, $this->aRequireModules)) {
            $this->aRequireModules[] = $sModule;
        }
    }

    /**
     *
     * @return array
     */
    public function GetRequireModules()
    {
        return $this->aRequireModules;
    }

    /**
     *
     * @return boolean
     */
    public function isPermanent()
    {
        return $this->bIsPermanent;
    }

    /**
     *
     * @return boolean
     */
    public function isValid()
    {
        return true;
    }

    /**
     *
     * @return boolean
     */
    protected function isAllowedModule()
    {
        return $this->isPermanent() || $this->oModuleManager->IsAllowedModule(self::GetName());
    }

    /**
     *
     * @return boolean
     */
    protected function isInitialized()
    {
        return (bool) $this->bInitialized;
    }

    protected function getNamespaceName()
    {
        $className = get_class($this);
        return str_contains($className, '\\')
            ? substr($className, 0, strrpos($className, '\\'))
            : null;
    }

    protected function initSubscriptions()
    {
        $subscriptionsClassName = $this->getNamespaceName() . "\\Subscriptions";
        if (class_exists($subscriptionsClassName)) {
            $subscriptions = new $subscriptionsClassName($this);
            $subscriptions->init();
        }
    }

    protected function initEntries()
    {
        $entitiesClassName = $this->getNamespaceName() . "\\Entries";
        if (class_exists($entitiesClassName)) {
            $entities = new $entitiesClassName($this);
            $entities->init();
        }
    }

    /**
     *
     * @return boolean
     */
    public function initialize()
    {
        $mResult = true;
        if (!$this->isInitialized()) {
            $this->bInitialized = true;
            $this->loadModuleSettings();
            $this->init();
            $this->initSubscriptions();
            $this->initEntries();
        }

        return $mResult;
    }

    /**
     *
     * @return string
     */
    final public static function getNamespace()
    {
        return \substr_replace(static::class, '', -7);
    }

    /**
     *
     * @return \Aurora\System\Module\Settings
     */
    public function getModuleSettings()
    {
        return $this->oModuleSettings;
    }

    /**
     *
     * @return \Aurora\System\Module\Settings
     */
    public function loadModuleSettings()
    {
        if (!isset($this->oModuleSettings)) {
            $this->oModuleSettings = & $this->GetModuleManager()->getModuleSettings(self::GetName());
            $this->oModuleSettings->Load();
        }
        return $this->oModuleSettings;
    }

    /**
     * Saves module settings to config.json file.
     *
     * returns bool
     */
    public function saveModuleConfig()
    {
        $bResult = false;

        if (isset($this->oModuleSettings)) {
            $bResult = $this->oModuleSettings->Save();
        }

        return $bResult;
    }

    /**
     *
     * @param string $sName
     * @param mixed $mDefaultValue
     * @return mixed
     */
    public function getConfig($sName, $mDefaultValue = null)
    {
        $mResult = $mDefaultValue;
        if (isset($this->oModuleSettings)) {
            $mResult = $this->oModuleSettings->GetValue($sName, $mDefaultValue);
        }

        return $mResult;
    }

    /**
     * Sets new value of module setting.
     *
     * @param string $sName Name of module setting.
     * @param string $sValue New value of module setting.
     *
     * @return boolean
     */
    public function setConfig($sName, $sValue = null)
    {
        $bResult = false;

        if (isset($this->oModuleSettings)) {
            $bResult = $this->oModuleSettings->SetValue($sName, $sValue);
        }

        return $bResult;
    }

    /**
     *
     * @param array $aMethods
     *
     */
    public function denyMethodsCallByWebApi($aMethods)
    {
        foreach ($aMethods as $sMethodName) {
            $this->denyMethodCallByWebApi($sMethodName);
        }
    }

    /**
     *
     * @param string $sMethodName
     *
     */
    public function denyMethodCallByWebApi($sMethodName)
    {
        if (!in_array($sMethodName, $this->aDeniedMethodsByWebApi)) {
            $this->aDeniedMethodsByWebApi[] = $sMethodName;
        }
    }

    /**
     *
     * @param string $sMethodName
     * @return boolean
     */
    protected function isDeniedMethodByWebApi($sMethodName)
    {
        return in_array($sMethodName, array_values($this->aDeniedMethodsByWebApi));
    }

    /**
     *
     * @param string $sMethod
     * @return boolean
     */
    protected function isEventCallback($sMethod)
    {
        return in_array($sMethod, $this->getEventsCallbacks());
    }

    /**
     *
     * @return array
     */
    protected function getEventsCallbacks()
    {
        $aEventsValues = array();
        $aEvents = $this->GetModuleManager()->getEvents();
        foreach (array_values($aEvents) as $aEvent) {
            foreach ($aEvent as $aEv) {
                if ($aEv[0]::GetName() === self::GetName()) {
                    $aEventsValues[] = $aEv[1];
                }
            }
        }

        return $aEventsValues;
    }

    /**
     *
     * @param string $sEvent
     * @param callable $fCallback
     * @param int $iPriority
     */
    public function subscribeEvent($sEvent, $fCallback, $iPriority = 100)
    {
        $this->GetModuleManager()->subscribeEvent($sEvent, $fCallback, $iPriority);
    }

    /**
     *
     * @param string $sEvent
     * @param array $aArguments
     */
    public function broadcastEvent($sEvent, &$aArguments = [], &$mResult = null)
    {
        if (!in_array($sEvent, $this->aSkipedEvents)) {
            return $this->GetModuleManager()->broadcastEvent(
                self::GetName(),
                $sEvent,
                $aArguments,
                $mResult
            );
        } else {
            $this->removeEventFromSkiped($sEvent);
        }
    }

    /**
     *
     * @param string $sEvent
     */
    public function skipEvent($sEvent)
    {
        if (!in_array($sEvent, $this->aSkipedEvents)) {
            $this->aSkipedEvents[] = $sEvent;
        }
    }

    /**
     *
     * @param string $sEvent
     */
    public function removeEventFromSkiped($sEvent)
    {
        $this->aSkipedEvents = array_diff(
            $this->aSkipedEvents,
            array($sEvent)
        );
    }

    /**
     * @param string $sParsedTemplateID
     * @param string $sParsedPlace
     * @param string $sTemplateFileName
     * @param string $sModuleName
     */
    public function includeTemplate($sParsedTemplateID, $sParsedPlace, $sTemplateFileName, $sModuleName = '')
    {
        if (0 < strlen($sParsedTemplateID) && 0 < strlen($sParsedPlace) && file_exists($this->GetPath() . '/' . $sTemplateFileName)) {
            $this->GetModuleManager()->includeTemplate(
                $sParsedTemplateID,
                $sParsedPlace,
                $this->GetPath() . '/' . $sTemplateFileName,
                $sModuleName
            );
        }
    }

    /**
     *
     * @param string $sType
     * @param array $aMap
     */
    public function extendObject($sType, $aMap)
    {
        $this->GetModuleManager()->extendObject(self::GetName(), $sType, $aMap);
    }

    /**
     *
     * @param string $sType
     * @return array
     */
    public function getExtendedObject($sType)
    {
        return $this->GetModuleManager()->getExtendedObject($sType);
    }

    /**
     *
     * @param string $sType
     * @return boolean
     */
    public function issetObject($sType)
    {
        return $this->GetModuleManager()->issetObject($sType);
    }

    /**
     * @param string $sPath
     */
    final public function SetPath($sPath)
    {
        $this->sPath = $sPath;
    }

    /**
     * @return string
     */
    final public function GetHash()
    {
        return '';
    }

    /**
     * @return string
     */
    final public static function GetName()
    {
        return substr(strrchr(static::getNamespace(), "\\"), 1);
    }

    public function getModuleName()
    {
        return static::GetName();
    }

    /**
     * @return string
     */
    final public function GetPath()
    {
        return $this->sPath;
    }

    /**
     * @return string
     */
    public function GetVersion()
    {
        return $this->sVersion;
    }

    /**
     * @return string
     */
    final public function GetFullName()
    {
        return self::GetName() . '-' . $this->sVersion;
    }

    /**
     *
     * @param string $sName
     * @param callable $mCallbak
     */
    final public function AddEntry($sName, $mCallbak)
    {
        \Aurora\System\Router::getInstance()->register(
            self::GetName(),
            $sName,
            [$this, $mCallbak]
        );
    }

    /**
     *
     * @param array $aEntries
     */
    final public function AddEntries($aEntries)
    {
        foreach ($aEntries as $sName => $mCallbak) {
            $this->AddEntry($sName, $mCallbak);
        }
    }

    /**
     *
     * @param string $sName
     * @return boolean
     */
    final public function HasEntry($sName)
    {
        return \Aurora\System\Router::getInstance()->hasRoute($sName);
    }

    /**
     *
     * @param string $sName
     */
    final public function RemoveEntry($sName)
    {
        return \Aurora\System\Router::getInstance()->removeRoute($sName);
    }

    /**
     *
     * @param array $aEntries
     */
    final public function RemoveEntries($aEntries)
    {
        foreach ($aEntries as $sName) {
            $this->RemoveEntry($sName);
        }
    }

    /**
     *
     * @param callable $mCallbak
     * @return boolean
     */
    protected function isEntryCallback($mCallbak)
    {
        return \Aurora\System\Router::getInstance()->hasCallback($mCallbak);
    }

    /**
     *
     * @param string $sName
     * @return mixed
     */
    final public function GetEntryCallback($sName)
    {
        return \Aurora\System\Router::getInstance()->getCallback($sName);
    }

    /**
     * @param string $sMethod
     * @param mixed $mResult = false
     *
     * @return array
     */
    final public function DefaultResponse($sMethod, $mResult = false)
    {
        return \Aurora\System\Managers\Response::DefaultResponse(self::GetName(), $sMethod, $mResult);
    }

    /**
     * @param string $sMethod
     *
     * @return array
     */
    final public function TrueResponse($sMethod)
    {
        return $this->DefaultResponse($sMethod, true);
    }

    /**
     * @param string $sMethod
     * @param int $iErrorCode
     * @param string $sErrorMessage
     * @param array $aAdditionalParams = null
     *
     * @return array
     */
    final public function FalseResponse($sMethod, $iErrorCode = null, $sErrorMessage = null, $aAdditionalParams = null, $sModule = null)
    {
        return \Aurora\System\Managers\Response::FalseResponse($sMethod, $iErrorCode, $sErrorMessage, $aAdditionalParams, $sModule);
    }

    /**
     * @param string $sActionName
     * @param \Exception $oException
     * @param array $aAdditionalParams = null
     *
     * @return array
     */
    final public function ExceptionResponse($sActionName, $oException, $aAdditionalParams = null)
    {
        return \Aurora\System\Managers\Response::ExceptionResponse($sActionName, $oException, $aAdditionalParams);
    }

    /**
     *
     * @param string $sMethodName
     * @param array $aArguments
     * @param boolean $bWebApi
     * @return array
     */
    protected function prepareMethodArguments($sMethodName, &$aArguments, $bWebApi)
    {
        $aMethodArgs = array();
        $oReflector = new \ReflectionMethod($this, $sMethodName);
        $aReflectionParameters = $oReflector->getParameters();
        if ($bWebApi) {
            foreach ($aReflectionParameters as $oParam) {
                $sParamName = $oParam->getName();
                $iParamPosition = $oParam->getPosition();
                $bIsArgumentGiven = array_key_exists($sParamName, $aArguments);
                if (!$bIsArgumentGiven && !$oParam->isDefaultValueAvailable()) {
                    $aMethodArgs[$iParamPosition] = null;
                } else {
                    $aMethodArgs[$iParamPosition] = $bIsArgumentGiven ?
                        $aArguments[$sParamName] : $oParam->getDefaultValue();
                }
            }
        } else {
            $aTempArguments = array();
            $aMethodArgs = $aArguments;
            foreach ($aReflectionParameters as $oParam) {
                $sParamName = $oParam->getName();
                $iParamPosition = $oParam->getPosition();
                $mArgumentValue = null;
                if (isset($aArguments[$iParamPosition])) {
                    $mArgumentValue = $aArguments[$iParamPosition];
                } elseif ($oParam->isDefaultValueAvailable()) {
                    $mArgumentValue = $oParam->getDefaultValue();
                }
                $aTempArguments[$sParamName] = $mArgumentValue;
            }
            $aArguments = $aTempArguments;
        }

        return $aMethodArgs;
    }

    /**
     *
     * @param string $sMethod
     * @return boolean
     */
    protected function isCallbackMethod($sMethod)
    {
        return ($this->isEntryCallback($sMethod) || $this->isEventCallback($sMethod));
    }

    protected function canCallMethod($sMethod, $bWebApi)
    {
        return !($bWebApi && ($this->isCallbackMethod($sMethod) || $this->isDeniedMethodByWebApi($sMethod))) && $this->isAllowedModule();
    }

    /**
     *
     * @param string $sMethod
     * @param array $aArguments
     * @param boolean $bWebApi
     * @return mixed
     */
    final public function CallMethod($sMethod, $aArguments = array(), $bWebApi = false)
    {
        $mResult = false;
        try {
            if (!method_exists($this, $sMethod)) {
                throw new \Aurora\System\Exceptions\ApiException(
                    \Aurora\System\Notifications::MethodNotFound
                );
            } elseif (!$this->canCallMethod($sMethod, $bWebApi)) {
                throw new \Aurora\System\Exceptions\ApiException(
                    \Aurora\System\Notifications::MethodAccessDenied
                );
            } else {
                if ($bWebApi && !isset($aArguments['UserId'])) {
                    $aArguments['UserId'] = \Aurora\System\Api::getAuthenticatedUserId();
                }

                // prepare arguments for before event
                $aMethodArgs = $this->prepareMethodArguments($sMethod, $aArguments, $bWebApi);

                $bEventResult = $this->broadcastEvent(
                    $sMethod . AbstractModule::$Delimiter . 'before',
                    $aArguments,
                    $mResult
                );

                // prepare arguments for main action after event
                $aMethodArgs = $this->prepareMethodArguments($sMethod, $aArguments, true);

                if (!$bEventResult) {
                    try {
                        $oReflector = new \ReflectionMethod($this, $sMethod);
                        if (!$oReflector->isPublic()) {
                            throw new \Aurora\System\Exceptions\ApiException(
                                \Aurora\System\Notifications::MethodAccessDenied
                            );
                        }
                        $mMethodResult = $this->$sMethod(...$aMethodArgs);

                        if (is_array($mMethodResult) && is_array($mResult)) {
                            $mResult = array_merge($mMethodResult, $mResult);
                        } elseif ($mMethodResult !== null) {
                            $mResult = $mMethodResult;
                        }
                    } catch (\Exception $oException) {
                        if ($oException instanceof \Illuminate\Database\QueryException) {
                            // throw new \Aurora\System\Exceptions\ApiException(
                            // 	$oException->getCode(),
                            // 	$oException,
                            // 	'Database is not configured'
                            // );
                            Api::LogException($oException);
                            $mResult = false;
                        } elseif (!($oException instanceof \Aurora\System\Exceptions\ApiException)) {
                            throw new \Aurora\System\Exceptions\ApiException(
                                $oException->getCode(),
                                $oException,
                                $oException->getMessage()
                            );
                        } else {
                            throw $oException;
                        }

                        $this->GetModuleManager()->AddResult(
                            self::GetName(),
                            $sMethod,
                            $aArguments,
                            $mResult,
                            $oException->getCode()
                        );
                    }
                }

                $this->broadcastEvent(
                    $sMethod . AbstractModule::$Delimiter . 'after',
                    $aArguments,
                    $mResult
                );

                $this->GetModuleManager()->AddResult(
                    self::GetName(),
                    $sMethod,
                    $aArguments,
                    $mResult
                );
            }
        } catch (\Exception $oException) {
            throw new \Aurora\System\Exceptions\ApiException(
                $oException->getCode(),
                $oException,
                $oException->getMessage(),
                [],
                $this
            );
        }

        return $mResult;
    }

    /**
     * Obtains list of module settings for authenticated user.
     *
     * @return array|null
     */
    public function GetSettings()
    {
        return null;
    }

    protected function getLangsData($sLang)
    {
        $mResult = false;
        $sLangFile = $this->GetPath() . '/i18n/' . $sLang . '.ini';
        $sLangFile = @\file_exists($sLangFile) ? $sLangFile : '';

        if (0 < \strlen($sLangFile)) {
            $aLang = \Aurora\System\Api::convertIniToLang($sLangFile);
            if (\is_array($aLang)) {
                $mResult = $aLang;
            }
        }
        return $mResult;
    }

    /**
     * @param string $sData
     * @param array $aParams = null
     * @param int $iPluralCount = null
     * @param int $iUserId = 0
     *
     * @return string
     */
    public function i18N($sData, $aParams = null, $iPluralCount = null, $iUserId = 0)
    {
        static $sLanguage = null;
        if (is_null($sLanguage)) {
            if ($iUserId > 0) {

                $oUser = Api::getUserById($iUserId);
                if ($oUser instanceof \Aurora\Modules\Core\Models\User) {
                    $sLanguage = $oUser->Language;
                }
            }
            if (empty($sLanguage)) {
                $sLanguage = \Aurora\System\Api::GetLanguage();
            }
        }

        if (is_null($this->aLang)) {
            if (isset(\Aurora\System\Api::$aClientI18N[self::GetName()][$sLanguage])) {
                $this->aLang = \Aurora\System\Api::$aClientI18N[self::GetName()][$sLanguage];
            } else {
                \Aurora\System\Api::$aClientI18N[self::GetName()][$sLanguage] = false;

                $this->aLang = $this->getLangsData($sLanguage);
                if (!$this->aLang) {
                    $this->aLang = $this->getLangsData('English');
                }

                if (\is_array($this->aLang)) {
                    \Aurora\System\Api::$aClientI18N[self::GetName()][$sLanguage] = $this->aLang;
                }
            }
            if (!isset($this->aLang[$sData])) {
                $this->aLang = $this->getLangsData('English');
            }
        }

        return isset($iPluralCount) ? \Aurora\System\Api::processTranslateParams($this->aLang, $sData, $aParams, \Aurora\System\Api::getPlural($sLanguage, $iPluralCount)) :
            \Aurora\System\Api::processTranslateParams($this->aLang, $sData, $aParams);
    }

    /**
     *
     * @param \Aurora\System\Classes\Model $oEntity
     */
    protected function updateEnabledForEntity(&$oEntity, $bEnabled = true)
    {
        if ($bEnabled) {
            $oEntity->enableModule(self::GetName());
        } else {
            $oEntity->disableModule(self::GetName());
        }
    }

    /**
     *
     * @param \Aurora\System\Classes\Model $oEntity
     * @return bool
     */
    protected function isEnabledForEntity(&$oEntity)
    {
        return !$oEntity->isModuleDisabled(self::GetName());
    }

    /**
     *
     * @return array
     */
    public function GetErrors()
    {
        return is_array($this->aErrors) ? (object) $this->aErrors : [];
    }

    /**
     * @param int $iErrorCode
     * @return string
     */
    public function GetErrorMessageByCode($iErrorCode)
    {
        return is_array($this->aErrors) && isset($this->aErrors[(int) $iErrorCode]) ? $this->aErrors[(int) $iErrorCode] : '';
    }

    /**
     *
     * @return array
     */
    public function GetAdditionalEntityFieldsToEdit()
    {
        return is_array($this->aAdditionalEntityFieldsToEdit) ? $this->aAdditionalEntityFieldsToEdit : [];
    }
}