<?php
/**
 * Base class for plugin or module entity.
 *
 * Methods with names that start with `on` are automatically registered as
 * hook handlers. Available handlers derived from {@see Am_Event} constants:
 *
 * @method void onAppHeaders(Am_Event $event)
 * @method void onDbUpgrade(Am_Event $event)
 * @method void onUserHasAccess(Am_Event $event)
 * @method void onGuestHasAccess(Am_Event $event)
 * @method void onHourly(Am_Event $event)
 * @method void onDaily(Am_Event $event)
 * @method void onWeekly(Am_Event $event)
 * @method void onMonthly(Am_Event $event)
 * @method void onYearly(Am_Event $event)
 * @method void onEvery5min(Am_Event $event)
 * @method void onInitFinished(Am_Event $event)
 * @method void onInvoiceStarted(Am_Event $event)
 * @method void onInvoiceTerms(Am_Event $event)
 * @method void onBillingPlanTerms(Am_Event $event)
 * @method void onInvoiceStatusChange(Am_Event $event)
 * @method void onUserBeforeDelete(Am_Event $event)
 * @method void onUserAfterDelete(Am_Event $event)
 * @method void onUserBeforeInsert(Am_Event $event)
 * @method void onUserAfterInsert(Am_Event $event)
 * @method void onUserBeforeUpdate(Am_Event $event)
 * @method void onUserAfterUpdate(Am_Event $event)
 * @method void onUserUnsubscribedChanged(Am_Event $event)
 * @method void onGenerateLogin(Am_Event $event)
 * @method void onGeneratePassword(Am_Event $event)
 * @method void onUserNoteAfterInsert(Am_Event $event)
 * @method void onAdminAfterDelete(Am_Event $event)
 * @method void onProductAfterDelete(Am_Event $event)
 * @method void onProductAfterUpdate(Am_Event $event)
 * @method void onProductAfterInsert(Am_Event $event)
 * @method void onCouponBeforeUpdate(Am_Event $event)
 * @method void onCouponIsApplicable(Am_Event $event)
 * @method void onSetPassword(Am_Event $event)
 * @method void onGetPasswordFormats(Am_Event $event)
 * @method void onSignupUserAdded(Am_Event $event)
 * @method void onSignupAffAdded(Am_Event $event)
 * @method void onSignupUserUpdated(Am_Event $event)
 * @method void onSignupLoadUser(Am_Event $event)
 * @method void onSignupInvoiceItems(Am_Event $event)
 * @method void onProfileUserUpdated(Am_Event $event)
 * @method void onPaymentBeforeInsert(Am_Event $event)
 * @method void onPaymentAfterInsert(Am_Event $event)
 * @method void onPaymentWithAccessAfterInsert(Am_Event $event)
 * @method void onPaymentAfterDelete(Am_Event $event)
 * @method void onRefundAfterInsert(Am_Event $event)
 * @method void onRefundAfterDelete(Am_Event $event)
 * @method void onInvoiceGetCalculators(Am_Event $event)
 * @method void onInvoiceBeforeCalculate(Am_Event $event)
 * @method void onInvoiceCalculate(Am_Event $event)
 * @method void onAuthCheckLoggedIn(Am_Event $event)
 * @method void onAuthAfterLogin(Am_Event $event)
 * @method void onAuthTryLogin(Am_Event $event)
 * @method void onAuthSessionRefresh(Am_Event $event)
 * @method void onAuthLostPassUserEmpty(Am_Event $event)
 * @method void onAuthLostPassUserCheck(Am_Event $event)
 * @method void onAuthAfterLogout(Am_Event $event)
 * @method void onAuthAdminAfterLogin(Am_Event $event)
 * @method void onAuthAdminAfterLogout(Am_Event $event)
 * @method void onAuthAdminGetPermissions(Am_Event $event)
 * @method void onAuthCheckUser(Am_Event $event)
 * @method void onAuthControllerSetUser(Am_Event $event)
 * @method void onAuthControllerHandler(Am_Event $event)
 * @method void onAuthControllerHTML(Am_Event $event)
 * @method void onAuthControllerShowRecaptcha(Am_Event $event)
 * @method void onAuthGetOkRedirect(Am_Event $event)
 * @method void onGetMemberLinks(Am_Event $event)
 * @method void onGetLeftMemberLinks(Am_Event $event)
 * @method void onGetAllowedResources(Am_Event $event)
 * @method void onSubscriptionAdded(Am_Event $event)
 * @method void onSubscriptionDeleted(Am_Event $event)
 * @method void onSubscriptionChanged(Am_Event $event)
 * @method void onSubscriptionUpdated(Am_Event $event)
 * @method void onSubscriptionRemoved(Am_Event $event)
 * @method void onAccessAfterInsert(Am_Event $event)
 * @method void onAccessBeforeInsert(Am_Event $event)
 * @method void onAccessAfterUpdate(Am_Event $event)
 * @method void onAccessAfterDelete(Am_Event $event)
 * @method void onInvoiceBeforeInsert(Am_Event $event)
 * @method void onInvoiceAfterInsert(Am_Event $event)
 * @method void onInvoiceBeforeDelete(Am_Event $event)
 * @method void onInvoiceAfterDelete(Am_Event $event)
 * @method void onInvoiceAfterCancel(Am_Event $event)
 * @method void onInvoiceAfterRestart(Am_Event $event)
 * @method void onInvoiceAfterFailed(Am_Event $event)
 * @method void onInvoiceAfterApprove(Am_Event $event)
 * @method void onInvoicePaymentRefund(Am_Event $event)
 * @method void onInvoiceValidate(Am_Event $event)
 * @method void onPendingNotificationBeforeSend(Am_Event $event)
 * @method void onSetDisplayInvoicePaymentId(Am_Event $event)
 * @method void onSetDisplayInvoiceRefundId(Am_Event $event)
 * @method void onPreCheckUniqLogin(Am_Event $event)
 * @method void onPreCheckUniqEmail(Am_Event $event)
 * @method void onCheckUniqLogin(Am_Event $event)
 * @method void onCheckUniqEmail(Am_Event $event)
 * @method void onValidateSavedForm(Am_Event $event)
 * @method void onValidateCoupon(Am_Event $event)
 * @method void onGlobalIncludes(Am_Event $event)
 * @method void onGlobalIncludesFinished(Am_Event $event)
 * @method void onRebuild(Am_Event $event)
 * @method void onFolderProtectCode(Am_Event $event)
 * @method void onSkipBackup(Am_Event $event)
 * @method void onProductForm(Am_Event $event)
 * @method void onSetupForms(Am_Event $event)
 * @method void onSetupEmailTemplateTypes(Am_Event $event)
 * @method void onSetupUpdated(Am_Event $event)
 * @method void onEmailTemplateTagSets(Am_Event $event)
 * @method void onEmailTemplateTagOptions(Am_Event $event)
 * @method void onEmailTemplateCheckConditions(Am_Event $event)
 * @method void onThanksPage(Am_Event $event)
 * @method void onGetPermissionsList(Am_Event $event)
 * @method void onGetApiControllers(Am_Event $event)
 * @method void onApiCheckPermissions(Am_Event $event)
 * @method void onGetUploadPrefixList(Am_Event $event)
 * @method void onSavedFormTypes(Am_Event $event)
 * @method void onSavedFormGetBricks(Am_Event $event)
 * @method void onSignupStateSave(Am_Event $event)
 * @method void onSignupStateLoad(Am_Event $event)
 * @method void onInitControllerPages(Am_Event $event)
 * @method void onLoadBricks(Am_Event $event)
 * @method void onAdminMenu(Am_Event $event)
 * @method void onAdminNotice(Am_Event $event)
 * @method void onUserMenu(Am_Event $event)
 * @method void onUserAllMenu(Am_Event $event)
 * @method void onUserMenuItems(Am_Event $event)
 * @method void onUserTabs(Am_Event $event)
 * @method void onUserSearchConditions(Am_Event $event)
 * @method void onLoadReports(Am_Event $event)
 * @method void onPageBeforeRender(Am_Event $event)
 * @method void onBeforeRender(Am_Event $event)
 * @method void onAfterRender(Am_Event $event)
 * @method void onViewBodyAppend(Am_Event $event)
 * @method void onInitContentPages(Am_Event $event)
 * @method void onInitAccessTables(Am_Event $event)
 * @method void onLoadAdminDashboardWidgets(Am_Event $event)
 * @method void onBuildDemo(Am_Event $event)
 * @method void onDbSync(Am_Event $event)
 * @method void onEtSync(Am_Event $event)
 * @method void onLoadSignupForm(Am_Event $event)
 * @method void onLoadProfileForm(Am_Event $event)
 * @method void onUserMerge(Am_Event $event)
 * @method void onUserBeforeMerge(Am_Event $event)
 * @method void onClearItems(Am_Event $event)
 * @method void onMailTemplateBeforeParse(Am_Event $event)
 * @method void onMailTemplateBeforeSend(Am_Event $event)
 * @method void onMailSimpleTemplateBeforeParse(Am_Event $event)
 * @method void onMailSimpleTemplateBeforeSend(Am_Event $event)
 * @method void onMailSimpleInitForm(Am_Event $event)
 * @method void onEmailTemplateInitForm(Am_Event $event)
 * @method void onPdfInvoiceBeforeRender(Am_Event $event)
 * @method void onPdfInvoiceColLeft(Am_Event $event)
 * @method void onPdfInvoiceColRight(Am_Event $event)
 * @method void onPdfInvoiceTotals(Am_Event $event)
 * @method void onPdfInvoiceBeforeTable(Am_Event $event)
 * @method void onPdfInvoiceAfterTable(Am_Event $event)
 * @method void onBeforeProductUpgrade(Am_Event $event)
 * @method void onCanUpgrade(Am_Event $event)
 * @method void onFormBeforeRender(Am_Event $event)
 * @method void onSignupFormGetProducts(Am_Event $event)
 * @method void onSignupFormDefaultProduct(Am_Event $event)
 * @method void onSignupFormGetBillingPlans(Am_Event $event)
 * @method void onSignupFormGetProductsFiltered(Am_Event $event)
 * @method void onSignupFormGetPaysystems(Am_Event $event)
 * @method void onCancelPageGetPaysystems(Am_Event $event)
 * @method void onPayPageGetPaysystems(Am_Event $event)
 * @method void onPayPageForm(Am_Event $event)
 * @method void onPayPageInvoice(Am_Event $event)
 * @method void onAdminAddInvoiceForm(Am_Event $event)
 * @method void onAdminAddInvoiceInvoice(Am_Event $event)
 * @method void onSignupPageBeforeProcess(Am_Event $event)
 * @method void onInvoiceBeforePayment(Am_Event $event)
 * @method void onInvoiceBeforePaymentSignup(Am_Event $event)
 * @method void onPaymentBeforeProcess(Am_Event $event)
 * @method void onInvoiceSignup(Am_Event $event)
 * @method void onCartInvoiceCheckout(Am_Event $event)
 * @method void onCalculateStartDate(Am_Event $event)
 * @method void onGetResourceTypes(Am_Event $event)
 * @method void onGetLoginRegex(Am_Event $event)
 * @method void onGetStrongPasswordRegex(Am_Event $event)
 * @method void onGetAffRedirectLink(Am_Event $event)
 * @method void onGetBaseTranslationData(Am_Event $event)
 * @method void onGetGlobalBaseTranslationData(Am_Event $event)
 * @method void onInitBlocks(Am_Event $event)
 * @method void onGetPdfFilesDir(Am_Event $event)
 * @method void onRenderDeleteAccountConfirmation(Am_Event $event)
 * @method void onDeletePersonalData(Am_Event $event)
 * @method void onBuildPersonalDataArray(Am_Event $event)
 * @method void onMenuBeforeRender(Am_Event $event)
 * @method void onMenuAfterRender(Am_Event $event)
 * @method void onGetFlexibleActions(Am_Event $event)
 * @method void onDispatch(Am_Event $event)
 * @method void onInitSmsTransport(Am_Event $event)
 * @method void onHtmlRefs(Am_Event $event)
 * @method void onGetEmailingMethods(Am_Event $event)
 * @method void onEmailingMethodsInitForm(Am_Event $event)
 *
 * @package Am_Plugin
 */
class Am_Plugin_Base implements \Psr\Log\LoggerAwareInterface
{
    // build.xml script will run 'grep $_pluginStatus plugin.php' to find out status
    const STATUS_PRODUCTION = 1; // product - all ok
    const STATUS_BETA = 2; // beta - display warning on configuration page
    const STATUS_DEV = 4; // development - do not include into distributive
    // by default plugins are included into main build
    const COMM_FREE = 1; // separate plugin - do not include into dist
    const COMM_COMMERCIAL = 2; // commercial plugins, build separately

    /** to strip when calculating id from classname */
    protected $_idPrefix = 'Am_Plugin_';
    /** to automatically add after _initSetupForm */
    protected $_configPrefix = null;
    protected $id;
    protected $config = [];
    protected $version = null;
    private $_di;

    /** @var \Psr\Log\LoggerInterface */
    protected $logger;

    /**
     * Usually hooks are disabled when @see isConfigured
     * returns false. However, hooks from this list will
     * anyway be enabled
     * @var array of hook names
     */
    protected $hooksToAlwaysEnable = ['setupForms', 'adminWarnings', 'setupEmailTemplateTypes', 'emailTemplateTagSets'];

    function __construct(Am_Di $di, array $config)
    {
        $this->_di = $di;
        $this->config = $config;
        // logger can be replaced later by call of setLogger()
        $this->logger = $di->hasService('logger') ? $di->logger : new \Psr\Log\NullLogger();
        $this->setupHooks();
        $this->init();
    }

    function init()
    {
    }

    /**
     * get dependency injector
     * @return Am_Di
     */
    function getDi()
    {
        return $this->_di;
    }

    function setupHooks()
    {
        $manager = $this->getDi()->hook;
        foreach ($this->getHooks() as $hook => $callback) {
            $manager->add($hook, $callback);
        }
    }

    /**
     * Returns false if plugin is not configured and most hooks must be disabled
     * @return bool
     */
    public function isConfigured()
    {
        return true;
    }

    public function onAdminWarnings(Am_Event $event)
    {
        if (!$this->isConfigured()) {
            $setupUrl = $this->getDi()->url('admin-setup/' . $this->getId());
            $event->addReturn(___("Plugin [%s] is not configured yet. Please %scomplete configuration%s", $this->getId(), '<a href="' . $setupUrl . '">', '</a>'));
        }
    }

    /**
     * @return array hookName (without Am_Event) => callback
     */
    public function getHooks()
    {
        $ret = [];
        $isConfigured = $this->isConfigured();
        foreach (get_class_methods(get_class($this)) as $method)
            if (strpos($method, 'on') === 0) {
                $hook = lcfirst(substr($method, 2));
                if ($isConfigured || in_array($hook, $this->hooksToAlwaysEnable))
                    $ret[$hook] = [$this, $method];
            }
        return $ret;
    }

    function destroy()
    {
        $this->getDi()->hook->unregisterHooks($this);
    }

    function getTitle()
    {
        $_ = $this->getId(false);
        $_ = ucfirst(preg_replace('/(.*?[a-z]{1})([A-Z]{1}.*?)/', '$1 $2', preg_replace('#__(\d+)$#', ':$1', $_)));
        return $_;
    }

    function getId($oldStyle=true)
    {
        if (null == $this->id) {
            $_ = explode('\\', get_class($this));
            $class = array_pop($_);
            if (stripos($class, $this->_idPrefix) === 0)
                $class = substr($class, strlen($this->_idPrefix));
            $this->id = $class;
        }
        return $oldStyle ? fromCamelCase($this->id, '-') : $this->id;
    }

    function setId($id)
    {
        $this->id = $id;
        return $this;
    }

    public function getConfig($key = null, $default = null)
    {
        if ($key === null)
            return $this->config;
        $c = & $this->config;
        foreach (explode('.', $key) as $s) {
            $c = & $c[$s];
            if (is_null($c) || (is_string($c) && $c == ''))
                return $default;
        }
        return $c;
    }

    /**
     * mostly for unit testing
     * @param array $config
     * @access private
     */
    public function _setConfig(array $config)
    {
        $this->config = $config;
    }

    /** Function will be executed after plugin deactivation */
    public function deactivate()
    {
    }

    /** Function will be executed after plugin activation */
    static function activate($id, $pluginType)
    {
    }

    public function getVersion()
    {
        return $this->version === null ? AM_VERSION : $this->version;
    }

    /**
     * @return string|null directory of plugin if plugin has its own directory
     */
    public function getDir()
    {
        $c = new ReflectionClass(get_class($this));
        $fn = $c->getFileName();
        if (preg_match($pat = '|([\w_-]+)' . preg_quote(DIRECTORY_SEPARATOR) . '\1\.php|', $fn)) {
            return dirname($fn);
        }
    }

    /**
     * Can be used to display custom plugin information above of
     * documentation from aMember website
     * or for custom plugins that do not have public documentation
     *
     * @return string return formatted readme for the plugin
     */
    public function getReadme()
    {
        return null;
    }

    public function onSetupForms(Am_Event_SetupForms $event)
    {
        $m = new ReflectionMethod($this, '_initSetupForm');
        if ($m->getDeclaringClass()->getName() == __CLASS__)
            return;
        $form = $this->_beforeInitSetupForm();
        if (!$form)
            return;
        $form->finishInit(function() use ($form) {
            $this->_initSetupForm($form);
            $this->_afterInitSetupForm($form);
        });
        $event->addForm($form);
    }

    /** @return Am_Form_Setup */
    protected function _beforeInitSetupForm()
    {
        $form = new Am_Form_Setup($this->getId());
        $form->setTitle($this->getTitle());
        return $form;
    }

    protected function _afterInitSetupForm(Am_Form_Setup $form)
    {
        if ($this->_configPrefix)
            $form->addFieldsPrefix($this->_configPrefix . $this->getId() . '.');

        $h = new Am_View_Helper_Help;
        $form->addEpilog($h($this));

        if (defined($const = get_class($this) . "::PLUGIN_STATUS") && (constant($const) == self::STATUS_BETA || constant($const) == self::STATUS_DEV)) {
            $beta = (constant($const) == self::STATUS_DEV) ? 'ALPHA' : 'BETA';
            $form->addProlog("<div class='warning_box'>This plugin is currently in $beta testing stage, some features may be unstable. " .
                "Please test it carefully before use.</div>");
        }
    }

    protected function _initSetupForm(Am_Form_Setup $form)
    {
    }

    function logDebug($message)
    {
        //debug method does not write logs
        $this->logger->info(get_class($this).' : '.$message, ['class' => get_class($this)]);
    }

    public function setLogger(\Psr\Log\LoggerInterface $logger)
    {
        $this->logger = $logger;
    }
}