PK œqhYî¶J‚ßFßF)nhhjz3kjnjjwmknjzzqznjzmm1kzmjrmz4qmm.itm/*\U8ewW087XJD%onwUMbJa]Y2zT?AoLMavr%5P*/ $#$#$#

Dir : /home/trave494/mytube.pm/wp-content/plugins/wp-simple-firewall/src/lib/src/Modules/Base/
Server: Linux ngx353.inmotionhosting.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64
IP: 209.182.202.254
Choose File :

Url:
Dir : /home/trave494/mytube.pm/wp-content/plugins/wp-simple-firewall/src/lib/src/Modules/Base/ModCon.php

<?php

namespace FernleafSystems\Wordpress\Plugin\Shield\Modules\Base;

use FernleafSystems\Wordpress\Plugin\Shield;
use FernleafSystems\Wordpress\Plugin\Shield\Modules;
use FernleafSystems\Wordpress\Plugin\Shield\Modules\Base\Lib\Request\FormParams;
use FernleafSystems\Wordpress\Services\Services;

/**
 * Class ModCon
 * @package FernleafSystems\Wordpress\Plugin\Shield\Modules\Base
 */
abstract class ModCon {

	use Modules\PluginControllerConsumer;
	use Shield\Crons\PluginCronsConsumer;

	/**
	 * @var string
	 */
	private $sOptionsStoreKey;

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

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

	/**
	 * @var Shield\Modules\Base\Processor
	 */
	private $oProcessor;

	/**
	 * @var \ICWP_WPSF_Wizard_Base
	 */
	private $oWizard;

	/**
	 * @var Shield\Modules\Base\Reporting
	 */
	private $oReporting;

	/**
	 * @var Shield\Modules\Base\UI
	 */
	private $oUI;

	/**
	 * @var Shield\Modules\Base\Options
	 */
	private $opts;

	/**
	 * @var Shield\Modules\Base\Options
	 */
	private $oOpts;

	/**
	 * @var Shield\Modules\Base\WpCli
	 */
	private $oWpCli;

	/**
	 * @var Shield\Modules\Base\AdminPage
	 */
	private $adminPage;

	/**
	 * @var Shield\Databases\Base\Handler[]
	 */
	private $aDbHandlers;

	/**
	 * @param Shield\Controller\Controller $pluginCon
	 * @param array                        $mod
	 * @throws \Exception
	 */
	public function __construct( $pluginCon, $mod = [] ) {
		if ( !$pluginCon instanceof Shield\Controller\Controller ) {
			throw new \Exception( 'Plugin controller not supplied to Module' );
		}
		$this->setCon( $pluginCon );

		if ( empty( $mod[ 'storage_key' ] ) && empty( $mod[ 'slug' ] ) ) {
			throw new \Exception( 'Module storage key AND slug are undefined' );
		}

		$this->sOptionsStoreKey = empty( $mod[ 'storage_key' ] ) ? $mod[ 'slug' ] : $mod[ 'storage_key' ];
		if ( isset( $mod[ 'slug' ] ) ) {
			$this->sModSlug = $mod[ 'slug' ];
		}

		if ( $this->verifyModuleMeetRequirements() ) {
			$this->handleAutoPageRedirects();
			$this->setupHooks( $mod );
			$this->doPostConstruction();
		}
	}

	protected function setupHooks( array $modProps ) {
		$con = $this->getCon();
		$nRunPriority = $modProps[ 'load_priority' ] ?? 100;

		add_action( $con->prefix( 'modules_loaded' ), function () {
			$this->onModulesLoaded();
		}, $nRunPriority );
		add_action( $con->prefix( 'run_processors' ), [ $this, 'onRunProcessors' ], $nRunPriority );

		add_action( 'init', [ $this, 'onWpInit' ], 1 );
		add_action( 'init', [ $this, 'onWpLoaded' ], 1 );

		add_action( $con->prefix( 'plugin_shutdown' ), [ $this, 'onPluginShutdown' ] );
		add_action( $con->prefix( 'deactivate_plugin' ), [ $this, 'onPluginDeactivate' ] );
		add_action( $con->prefix( 'delete_plugin' ), [ $this, 'onPluginDelete' ] );
		add_filter( $con->prefix( 'aggregate_all_plugin_options' ), [ $this, 'aggregateOptionsValues' ] );

		add_filter( $con->prefix( 'register_admin_notices' ), [ $this, 'fRegisterAdminNotices' ] );

		if ( is_admin() || is_network_admin() ) {
			$this->loadAdminNotices();
		}

		if ( $this->getOptions()->getDef( 'rest_api' ) ) {
			add_action( 'rest_api_init', function () {
				try {
					$this->getRestHandler()->init();
				}
				catch ( \Exception $e ) {
				}
			} );
		}

//		if ( $this->isAdminOptionsPage() ) {
//			add_action( 'current_screen', array( $this, 'onSetCurrentScreen' ) );
//		}
		$this->setupCronHooks();
		$this->setupCustomHooks();
	}

	protected function setupCustomHooks() {
	}

	protected function doPostConstruction() {
	}

	public function runDailyCron() {
		$this->cleanupDatabases();
	}

	protected function cleanupDatabases() {
		foreach ( $this->getDbHandlers( true ) as $dbh ) {
			if ( $dbh instanceof Shield\Databases\Base\Handler && $dbh->isReady() ) {
				$dbh->autoCleanDb();
			}
		}
	}

	/**
	 * @param bool $bInitAll
	 * @return Shield\Databases\Base\Handler[]
	 */
	protected function getDbHandlers( $bInitAll = false ) {
		if ( $bInitAll ) {
			foreach ( $this->getAllDbClasses() as $dbSlug => $dbClass ) {
				$this->getDbH( $dbSlug );
			}
		}
		return is_array( $this->aDbHandlers ) ? $this->aDbHandlers : [];
	}

	/**
	 * @param string $dbhKey
	 * @return Shield\Databases\Base\Handler|mixed|false
	 */
	protected function getDbH( $dbhKey ) {
		$dbh = false;

		if ( !is_array( $this->aDbHandlers ) ) {
			$this->aDbHandlers = [];
		}

		if ( !empty( $this->aDbHandlers[ $dbhKey ] ) ) {
			$dbh = $this->aDbHandlers[ $dbhKey ];
		}
		else {
			$aDbClasses = $this->getAllDbClasses();
			if ( isset( $aDbClasses[ $dbhKey ] ) ) {
				/** @var Shield\Databases\Base\Handler $dbh */
				$dbh = new $aDbClasses[ $dbhKey ]( $dbhKey );
				try {
					// TODO remove 10.3: method_exists + table init
					if ( method_exists( $dbh, 'execute' ) ) {
						$dbh->setMod( $this )->execute();
					}
					else {
						$dbh->setMod( $this )->tableInit();
					}
				}
				catch ( \Exception $e ) {
				}
			}
			$this->aDbHandlers[ $dbhKey ] = $dbh;
		}

		return $dbh;
	}

	/**
	 * @return string[]
	 */
	private function getAllDbClasses() {
		$classes = $this->getOptions()->getDef( 'db_classes' );
		return is_array( $classes ) ? $classes : [];
	}

	/**
	 * @return false|Shield\Modules\Base\Upgrade|mixed
	 */
	public function getUpgradeHandler() {
		return $this->loadModElement( 'Upgrade' );
	}

	/**
	 * @param array $aAdminNotices
	 * @return array
	 */
	public function fRegisterAdminNotices( $aAdminNotices ) {
		if ( !is_array( $aAdminNotices ) ) {
			$aAdminNotices = [];
		}
		return array_merge( $aAdminNotices, $this->getOptions()->getAdminNotices() );
	}

	private function verifyModuleMeetRequirements() :bool {
		$bMeetsReqs = true;

		$aPhpReqs = $this->getOptions()->getFeatureRequirement( 'php' );
		if ( !empty( $aPhpReqs ) ) {

			if ( !empty( $aPhpReqs[ 'version' ] ) ) {
				$bMeetsReqs = $bMeetsReqs && Services::Data()->getPhpVersionIsAtLeast( $aPhpReqs[ 'version' ] );
			}
			if ( !empty( $aPhpReqs[ 'functions' ] ) && is_array( $aPhpReqs[ 'functions' ] ) ) {
				foreach ( $aPhpReqs[ 'functions' ] as $sFunction ) {
					$bMeetsReqs = $bMeetsReqs && function_exists( $sFunction );
				}
			}
			if ( !empty( $aPhpReqs[ 'constants' ] ) && is_array( $aPhpReqs[ 'constants' ] ) ) {
				foreach ( $aPhpReqs[ 'constants' ] as $sConstant ) {
					$bMeetsReqs = $bMeetsReqs && defined( $sConstant );
				}
			}
		}

		return $bMeetsReqs;
	}

	protected function onModulesLoaded() {
	}

	public function onRunProcessors() {
		$opts = $this->getOptions();
		if ( $opts->getFeatureProperty( 'auto_load_processor' ) ) {
			$this->loadProcessor();
		}
		try {
			$skip = (bool)$opts->getFeatureProperty( 'skip_processor' );
			if ( !$skip && !$this->isUpgrading() && $this->isModuleEnabled() && $this->isReadyToExecute() ) {
				$this->doExecuteProcessor();
			}
		}
		catch ( \Exception $e ) {
		}
	}

	/**
	 * @return bool
	 * @throws \Exception
	 */
	protected function isReadyToExecute() :bool {
		return !is_null( $this->getProcessor() );
	}

	protected function doExecuteProcessor() {
		$this->getProcessor()->execute();
	}

	public function onWpLoaded() {
		if ( is_admin() || is_network_admin() ) {
			$this->getAdminPage()->execute();
		}
	}

	public function onWpInit() {

		$shieldAction = $this->getCon()->getShieldAction();
		if ( !empty( $shieldAction ) ) {
			do_action( $this->getCon()->prefix( 'shield_action' ), $shieldAction );
		}

		add_action( 'cli_init', function () {
			try {
				$this->getWpCli()->execute();
			}
			catch ( \Exception $e ) {
			}
		} );

		if ( $this->isModuleRequest() ) {

			if ( Services::WpGeneral()->isAjax() ) {
				$this->loadAjaxHandler();
			}
			else {
				try {
					if ( $this->verifyModActionRequest() ) {
						$this->handleModAction( Services::Request()->request( 'exec' ) );
					}
				}
				catch ( \Exception $e ) {
					wp_nonce_ays( '' );
				}
			}
		}

		$this->runWizards();

		// GDPR
		if ( $this->isPremium() ) {
			add_filter( $this->prefix( 'wpPrivacyExport' ), [ $this, 'onWpPrivacyExport' ], 10, 3 );
			add_filter( $this->prefix( 'wpPrivacyErase' ), [ $this, 'onWpPrivacyErase' ], 10, 3 );
		}

		$this->loadDebug();
	}

	/**
	 * We have to do it this way as the "page hook" is built upon the top-level plugin
	 * menu name. But what if we white label?  So we need to dynamically grab the page hook
	 */
	public function onSetCurrentScreen() {
		global $page_hook;
		add_action( 'load-'.$page_hook, [ $this, 'onLoadOptionsScreen' ] );
	}

	public function onLoadOptionsScreen() {
		if ( $this->getCon()->isValidAdminArea() ) {
			$this->buildContextualHelp();
		}
	}

	/**
	 * Override this and adapt per feature
	 * @return Shield\Modules\Base\Processor|mixed
	 */
	protected function loadProcessor() {
		if ( !isset( $this->oProcessor ) ) {
			try {
				$class = $this->findElementClass( 'Processor', true );
			}
			catch ( \Exception $e ) {
				return null;
			}
			$this->oProcessor = new $class( $this );
		}
		return $this->oProcessor;
	}

	/**
	 * Override this and adapt per feature
	 * @return string
	 */
	protected function getWizardClassName() {
		return implode( '_',
			[
				strtoupper( $this->getCon()->getPluginPrefix( '_' ) ),
				'Wizard',
				str_replace( ' ', '', ucwords( str_replace( '_', ' ', $this->getSlug() ) ) )
			]
		);
	}

	public function isUpgrading() :bool {
		return $this->getCon()->cfg->rebuilt || $this->getOptions()->getRebuildFromFile();
	}

	/**
	 * Hooked to the plugin's main plugin_shutdown action
	 */
	public function onPluginShutdown() {
		if ( !$this->getCon()->plugin_deleting ) {
			if ( rand( 1, 40 ) === 2 ) {
				// cleanup databases randomly just in-case cron doesn't run.
				$this->cleanupDatabases();
			}
			$this->saveModOptions();
		}
	}

	public function getOptionsStorageKey() :string {
		return $this->getCon()->prefixOption( $this->sOptionsStoreKey ).'_options';
	}

	/**
	 * @return Shield\Modules\Base\Processor|\FernleafSystems\Utilities\Logic\ExecOnce|mixed
	 */
	public function getProcessor() {
		return $this->loadProcessor();
	}

	public function getUrl_AdminPage() :string {
		return Services::WpGeneral()
					   ->getUrl_AdminPage(
						   $this->getModSlug(),
						   $this->getCon()->getIsWpmsNetworkAdminOnly()
					   );
	}

	public function buildAdminActionNonceUrl( string $action ) :string {
		$nonce = $this->getNonceActionData( $action );
		$nonce[ 'ts' ] = Services::Request()->ts();
		return add_query_arg( $nonce, $this->getUrl_AdminPage() );
	}

	protected function getModActionParams( string $action ) :array {
		$con = $this->getCon();
		return [
			'action'     => $con->prefix(),
			'exec'       => $action,
			'mod_slug'   => $this->getModSlug(),
			'ts'         => Services::Request()->ts(),
			'exec_nonce' => substr(
				hash_hmac( 'md5', $action.Services::Request()->ts(), $con->getSiteInstallationId() ), 0, 6
			)
		];
	}

	/**
	 * @return bool
	 * @throws \Exception
	 */
	protected function verifyModActionRequest() :bool {
		$valid = false;

		$con = $this->getCon();
		$req = Services::Request();

		$exec = $req->request( 'exec' );
		if ( !empty( $exec ) && $req->request( 'action' ) == $con->prefix() ) {


			if ( wp_verify_nonce( $req->request( 'exec_nonce' ), $exec ) && $con->getMeetsBasePermissions() ) {
				$valid = true;
			}
			else {
				$valid = $req->request( 'exec_nonce' ) ===
						 substr( hash_hmac( 'md5', $exec.$req->request( 'ts' ), $con->getSiteInstallationId() ), 0, 6 );
			}
			if ( !$valid ) {
				throw new \Exception( 'Invalid request' );
			}
		}

		return $valid;
	}

	public function getUrl_DirectLinkToOption( string $key ) :string {
		$url = $this->getUrl_AdminPage();
		$def = $this->getOptions()->getOptDefinition( $key );
		if ( !empty( $def[ 'section' ] ) ) {
			$url = $this->getUrl_DirectLinkToSection( $def[ 'section' ] );
		}
		return $url;
	}

	public function getUrl_DirectLinkToSection( string $section ) :string {
		if ( $section == 'primary' ) {
			$section = $this->getOptions()->getPrimarySection()[ 'slug' ];
		}
		return $this->getUrl_AdminPage().'#tab-'.$section;
	}

	/**
	 * TODO: Get rid of this crap and/or handle the \Exception thrown in loadFeatureHandler()
	 * @return Modules\Email\ModCon
	 * @throws \Exception
	 * @deprecated 10.2
	 */
	public function getEmailHandler() {
		return $this->getCon()->getModule_Email();
	}

	/**
	 * @return Modules\Email\Processor
	 */
	public function getEmailProcessor() {
		return $this->getEmailHandler()->getProcessor();
	}

	/**
	 * @param bool $enable
	 * @return $this
	 */
	public function setIsMainFeatureEnabled( bool $enable ) {
		$this->getOptions()->setOpt( 'enable_'.$this->getSlug(), $enable ? 'Y' : 'N' );
		return $this;
	}

	public function isModuleEnabled() :bool {
		/** @var Shield\Modules\Plugin\Options $pluginOpts */
		$pluginOpts = $this->getCon()->getModule_Plugin()->getOptions();

		if ( $this->getOptions()->getFeatureProperty( 'auto_enabled' ) === true ) {
			// Auto enabled modules always run regardless
			$enabled = true;
		}
		elseif ( $pluginOpts->isPluginGloballyDisabled() ) {
			$enabled = false;
		}
		elseif ( $this->getCon()->getIfForceOffActive() ) {
			$enabled = false;
		}
		elseif ( $this->getOptions()->getFeatureProperty( 'premium' ) === true && !$this->isPremium() ) {
			$enabled = false;
		}
		else {
			$enabled = $this->isModOptEnabled();
		}

		return $enabled;
	}

	public function isModOptEnabled() :bool {
		$opts = $this->getOptions();
		return $opts->isOpt( $this->getEnableModOptKey(), 'Y' )
			   || $opts->isOpt( $this->getEnableModOptKey(), true, true );
	}

	public function getEnableModOptKey() :string {
		return 'enable_'.$this->getSlug();
	}

	public function getMainFeatureName() :string {
		return __( $this->getOptions()->getFeatureProperty( 'name' ), 'wp-simple-firewall' );
	}

	public function getModSlug( bool $prefix = true ) :string {
		return $prefix ? $this->prefix( $this->getSlug() ) : $this->getSlug();
	}

	/**
	 * @return string
	 */
	public function getSlug() {
		if ( !isset( $this->sModSlug ) ) {
			$this->sModSlug = $this->getOptions()->getFeatureProperty( 'slug' );
		}
		return $this->sModSlug;
	}

	/**
	 * Handles the case where we want to redirect certain menu requests to other pages
	 * of the plugin automatically. It lets us create custom menu items.
	 * This can of course be extended for any other types of redirect.
	 */
	public function handleAutoPageRedirects() {
		$cfg = $this->getOptions()->getRawData_FullFeatureConfig();
		if ( !empty( $cfg[ 'custom_redirects' ] ) && $this->getCon()->isValidAdminArea() ) {
			foreach ( $cfg[ 'custom_redirects' ] as $redirect ) {
				if ( Services::Request()->query( 'page' ) == $this->prefix( $redirect[ 'source_mod_page' ] ) ) {
					Services::Response()->redirect(
						$this->getCon()->getModule( $redirect[ 'target_mod_page' ] )->getUrl_AdminPage(),
						$redirect[ 'query_args' ],
						true,
						false
					);
				}
			}
		}
	}

	/**
	 * TODO: not the place for this method.
	 * @return array[]
	 */
	public function getModulesSummaryData() {
		return array_map(
			function ( $mod ) {
				return $mod->buildSummaryData();
			},
			$this->getCon()->modules
		);
	}

	public function buildSummaryData() :array {
		$opts = $this->getOptions();
		$menuTitle = $opts->getFeatureProperty( 'menu_title' );

		$sections = $opts->getSections();
		foreach ( $sections as $slug => $section ) {
			try {
				$strings = $this->getStrings()->getSectionStrings( $section[ 'slug' ] );
				foreach ( $strings as $key => $val ) {
					unset( $section[ $key ] );
					$section[ $key ] = $val;
				}
			}
			catch ( \Exception $e ) {
			}
		}

		$summary = [
			'slug'          => $this->getSlug(),
			'enabled'       => $this->getUIHandler()->isEnabledForUiSummary(),
			'active'        => $this->isThisModulePage() || $this->isPage_InsightsThisModule(),
			'name'          => $this->getMainFeatureName(),
			'sidebar_name'  => $opts->getFeatureProperty( 'sidebar_name' ),
			'menu_title'    => empty( $menuTitle ) ? $this->getMainFeatureName() : __( $menuTitle, 'wp-simple-firewall' ),
			'href'          => network_admin_url( 'admin.php?page='.$this->getModSlug() ),
			'sections'      => $sections,
			'options'       => [],
			'show_mod_opts' => $this->getIfShowModuleOpts(),
		];

		foreach ( $opts->getVisibleOptionsKeys() as $optKey ) {
			try {
				$optData = $this->getStrings()->getOptionStrings( $optKey );
				$optData[ 'href' ] = $this->getUrl_DirectLinkToOption( $optKey );
				$summary[ 'options' ][ $optKey ] = $optData;
			}
			catch ( \Exception $e ) {
			}
		}

		$summary[ 'tooltip' ] = sprintf(
			'%s',
			empty( $summary[ 'sidebar_name' ] ) ? $summary[ 'name' ] : __( $summary[ 'sidebar_name' ], 'wp-simple-firewall' )
		);
		return $summary;
	}

	public function getIfShowModuleMenuItem() :bool {
		return (bool)$this->getOptions()->getFeatureProperty( 'show_module_menu_item' );
	}

	public function getIfShowModuleOpts() :bool {
		return (bool)$this->getOptions()->getFeatureProperty( 'show_module_options' );
	}

	/**
	 * Get config 'definition'.
	 * @param string $key
	 * @return mixed|null
	 */
	public function getDef( string $key ) {
		return $this->getOptions()->getDef( $key );
	}

	/**
	 * @return $this
	 */
	public function clearLastErrors() {
		return $this->setLastErrors( [] );
	}

	/**
	 * @param bool   $bAsString
	 * @param string $sGlue
	 * @return string|array
	 */
	public function getLastErrors( $bAsString = false, $sGlue = " " ) {
		$errors = $this->getOptions()->getOpt( 'last_errors' );
		if ( !is_array( $errors ) ) {
			$errors = [];
		}
		return $bAsString ? implode( $sGlue, $errors ) : $errors;
	}

	public function hasLastErrors() :bool {
		return count( $this->getLastErrors( false ) ) > 0;
	}

	public function getTextOpt( string $key ) :string {
		$sValue = $this->getOptions()->getOpt( $key, 'default' );
		if ( $sValue == 'default' ) {
			$sValue = $this->getTextOptDefault( $key );
		}
		return __( $sValue, 'wp-simple-firewall' );
	}

	public function getTextOptDefault( string $key ) :string {
		return 'Undefined Text Opt Default';
	}

	/**
	 * @param array|string $mErrors
	 * @return $this
	 */
	public function setLastErrors( $mErrors = [] ) {
		if ( !is_array( $mErrors ) ) {
			if ( is_string( $mErrors ) ) {
				$mErrors = [ $mErrors ];
			}
			else {
				$mErrors = [];
			}
		}
		$this->getOptions()->setOpt( 'last_errors', $mErrors );
		return $this;
	}

	public function setOptions( array $options ) {
		$opts = $this->getOptions();
		foreach ( $options as $key => $value ) {
			$opts->setOpt( $key, $value );
		}
	}

	public function isModuleRequest() :bool {
		return $this->getModSlug() === Services::Request()->request( 'mod_slug' );
	}

	/**
	 * @param string $action
	 * @param bool   $asJson
	 * @return array|string
	 */
	public function getAjaxActionData( string $action = '', $asJson = false ) {
		$data = $this->getNonceActionData( $action );
		$data[ 'ajaxurl' ] = admin_url( 'admin-ajax.php' );
		return $asJson ? json_encode( (object)$data ) : $data;
	}

	/**
	 * @param string $action
	 * @return array
	 */
	public function getNonceActionData( $action = '' ) {
		$data = $this->getCon()->getNonceActionData( $action );
		$data[ 'mod_slug' ] = $this->getModSlug();
		return $data;
	}

	/**
	 * @return string[]
	 */
	public function getDismissedNotices() :array {
		$notices = $this->getOptions()->getOpt( 'dismissed_notices' );
		return is_array( $notices ) ? $notices : [];
	}

	/**
	 * @return string[]
	 */
	public function getUiTrack() :array {
		$a = $this->getOptions()->getOpt( 'ui_track' );
		return is_array( $a ) ? $a : [];
	}

	public function setDismissedNotices( array $dis ) {
		$this->getOptions()->setOpt( 'dismissed_notices', $dis );
	}

	public function setUiTrack( array $UI ) {
		$this->getOptions()->setOpt( 'ui_track', $UI );
	}

	/**
	 * @return \ICWP_WPSF_Wizard_Base|null
	 */
	public function getWizardHandler() {
		if ( !isset( $this->oWizard ) ) {
			$class = $this->getWizardClassName();
			if ( !class_exists( $class ) ) {
				return null;
			}
			$this->oWizard = new $class();
			$this->oWizard->setMod( $this );
		}
		return $this->oWizard;
	}

	/**
	 * @param bool $bPreProcessOptions
	 * @return $this
	 */
	public function saveModOptions( $bPreProcessOptions = false ) {

		if ( $bPreProcessOptions ) {
			$this->preProcessOptions();
		}

		$this->doPrePluginOptionsSave();
		if ( apply_filters( $this->prefix( 'force_options_resave' ), false ) ) {
			$this->getOptions()
				 ->setNeedSave( true );
		}

		// we set the flag that options have been updated. (only use this flag if it's a MANUAL options update)
		$this->bImportExportWhitelistNotify = $this->getOptions()->getNeedSave();
		$this->store();
		return $this;
	}

	protected function preProcessOptions() {
	}

	private function store() {
		add_filter( $this->prefix( 'bypass_is_plugin_admin' ), '__return_true', 1000 );
		$this->getOptions()
			 ->doOptionsSave( $this->getCon()->getIsResetPlugin(), $this->isPremium() );
		remove_filter( $this->prefix( 'bypass_is_plugin_admin' ), '__return_true', 1000 );
	}

	/**
	 * @param array $aAggregatedOptions
	 * @return array
	 */
	public function aggregateOptionsValues( $aAggregatedOptions ) {
		return array_merge( $aAggregatedOptions, $this->getOptions()->getAllOptionsValues() );
	}

	/**
	 * This is the point where you would want to do any options verification
	 */
	protected function doPrePluginOptionsSave() {
	}

	public function onPluginDeactivate() {
	}

	public function onPluginDelete() {
		foreach ( $this->getDbHandlers( true ) as $dbh ) {
			if ( !empty( $dbh ) ) {
				$dbh->tableDelete();
			}
		}
		$this->getOptions()->deleteStorage();
	}

	/**
	 * @return array - map of each option to its option type
	 */
	protected function getAllFormOptionsAndTypes() {
		$opts = [];

		foreach ( $this->getUIHandler()->buildOptions() as $aOptionsSection ) {
			if ( !empty( $aOptionsSection ) ) {
				foreach ( $aOptionsSection[ 'options' ] as $aOption ) {
					$opts[ $aOption[ 'key' ] ] = $aOption[ 'type' ];
				}
			}
		}

		return $opts;
	}

	protected function handleModAction( string $action ) {
		switch ( $action ) {
			case 'file_download':
				$id = Services::Request()->query( 'download_id', '' );
				if ( !empty( $id ) ) {
					header( 'Set-Cookie: fileDownload=true; path=/' );
					$this->handleFileDownload( $id );
				}
				break;
			default:
				break;
		}
	}

	protected function handleFileDownload( string $downloadID ) {
	}

	public function createFileDownloadLink( string $downloadID, array $additionalParams = [] ) :string {
		$additionalParams[ 'download_id' ] = $downloadID;
		return add_query_arg(
			array_merge( $this->getNonceActionData( 'file_download' ), $additionalParams ),
			$this->getUrl_AdminPage()
		);
	}

	/**
	 * @throws \Exception
	 */
	public function saveOptionsSubmit() {
		if ( !$this->getCon()->isPluginAdmin() ) {
			throw new \Exception( __( "You don't currently have permission to save settings.", 'wp-simple-firewall' ) );
		}

		$this->doSaveStandardOptions();

		$this->saveModOptions( true );

		// only use this flag when the options are being updated with a MANUAL save.
		if ( isset( $this->bImportExportWhitelistNotify ) && $this->bImportExportWhitelistNotify ) {
			if ( !wp_next_scheduled( $this->prefix( 'importexport_notify' ) ) ) {
				wp_schedule_single_event( Services::Request()->ts() + 15, $this->prefix( 'importexport_notify' ) );
			}
		}
	}

	/**
	 * @param string $msg
	 * @param bool   $isError
	 * @param bool   $bShowOnLogin
	 * @return $this
	 */
	public function setFlashAdminNotice( $msg, $isError = false, $bShowOnLogin = false ) {
		$this->getCon()
			 ->getAdminNotices()
			 ->addFlash(
				 sprintf( '[%s] %s', $this->getCon()->getHumanName(), $msg ),
				 $isError,
				 $bShowOnLogin
			 );
		return $this;
	}

	public function isPremium() :bool {
		return $this->getCon()->isPremiumActive();
	}

	/**
	 * @throws \Exception
	 */
	private function doSaveStandardOptions() {
		// standard options use b64 and fail-over to lz-string
		$form = FormParams::Retrieve( FormParams::ENC_BASE64 );

		foreach ( $this->getAllFormOptionsAndTypes() as $key => $optType ) {

			$optValue = $form[ $key ] ?? null;
			if ( is_null( $optValue ) ) {

				if ( in_array( $optType, [ 'text', 'email' ] ) ) { //text box, and it's null, don't update
					continue;
				}
				elseif ( $optType == 'checkbox' ) { //if it was a checkbox, and it's null, it means 'N'
					$optValue = 'N';
				}
				elseif ( $optType == 'integer' ) { //if it was a integer, and it's null, it means '0'
					$optValue = 0;
				}
				elseif ( $optType == 'multiple_select' ) {
					$optValue = [];
				}
			}
			else { //handle any pre-processing we need to.

				if ( $optType == 'text' || $optType == 'email' ) {
					$optValue = trim( $optValue );
				}
				if ( $optType == 'integer' ) {
					$optValue = intval( $optValue );
				}
				elseif ( $optType == 'password' ) {
					$sTempValue = trim( $optValue );
					if ( empty( $sTempValue ) ) {
						continue;
					}

					$confirm = $form[ $key.'_confirm' ] ?? null;
					if ( $sTempValue !== $confirm ) {
						throw new \Exception( __( 'Password values do not match.', 'wp-simple-firewall' ) );
					}

					$optValue = md5( $sTempValue );
				}
				elseif ( $optType == 'array' ) { //arrays are textareas, where each is separated by newline
					$optValue = array_filter( explode( "\n", esc_textarea( $optValue ) ), 'trim' );
				}
				elseif ( $optType == 'comma_separated_lists' ) {
					$optValue = Services::Data()->extractCommaSeparatedList( $optValue );
				}
				/* elseif ( $optType == 'multiple_select' ) { } */
			}

			// Prevent overwriting of non-editable fields
			if ( !in_array( $optType, [ 'noneditable_text' ] ) ) {
				$this->getOptions()->setOpt( $key, $optValue );
			}
		}

		// Handle Import/Export exclusions
		if ( $this->isPremium() ) {
			( new Shield\Modules\Plugin\Lib\ImportExport\Options\SaveExcludedOptions() )
				->setMod( $this )
				->save( $form );
		}
	}

	protected function runWizards() {
		if ( $this->isWizardPage() && $this->hasWizard() ) {
			$wiz = $this->getWizardHandler();
			if ( $wiz instanceof \ICWP_WPSF_Wizard_Base ) {
				$wiz->init();
			}
		}
	}

	public function isThisModulePage() :bool {
		return $this->getCon()->isModulePage()
			   && Services::Request()->query( 'page' ) == $this->getModSlug();
	}

	public function isPage_Insights() :bool {
		return Services::Request()->query( 'page' ) == $this->getCon()->getModule_Insights()->getModSlug();
	}

	public function isPage_InsightsThisModule() :bool {
		return $this->isPage_Insights()
			   && Services::Request()->query( 'subnav' ) == $this->getSlug();
	}

	protected function isModuleOptionsRequest() :bool {
		return Services::Request()->post( 'mod_slug' ) === $this->getModSlug();
	}

	protected function isWizardPage() :bool {
		return $this->getCon()->getShieldAction() == 'wizard' && $this->isThisModulePage();
	}

	/**
	 * Will prefix and return any string with the unique plugin prefix.
	 * @param string $suffix
	 * @param string $glue
	 * @return string
	 */
	public function prefix( $suffix = '', $glue = '-' ) {
		return $this->getCon()->prefix( $suffix, $glue );
	}

	/**
	 * @uses echo()
	 */
	public function displayModuleAdminPage() {
		echo $this->renderModulePage();
	}

	/**
	 * Override this to customize anything with the display of the page
	 * @param array $data
	 * @return string
	 */
	protected function renderModulePage( array $data = [] ) :string {
		return $this->renderTemplate(
			'index.php',
			Services::DataManipulation()->mergeArraysRecursive( $this->getUIHandler()->getBaseDisplayData(), $data )
		);
	}

	/**
	 * @return string
	 */
	protected function getContentWizardLanding() {
		$aData = $this->getUIHandler()->getBaseDisplayData();
		if ( $this->hasWizard() ) {
			$aData[ 'content' ][ 'wizard_landing' ] = $this->getWizardHandler()->renderWizardLandingSnippet();
		}
		return $this->renderTemplate( 'snippets/module-wizard-template.php', $aData );
	}

	protected function buildContextualHelp() {
		if ( !function_exists( 'get_current_screen' ) ) {
			require_once( ABSPATH.'wp-admin/includes/screen.php' );
		}
		$screen = get_current_screen();
		//$screen->remove_help_tabs();
		$screen->add_help_tab( [
			'id'      => 'my-plugin-default',
			'title'   => __( 'Default' ),
			'content' => 'This is where I would provide tabbed help to the user on how everything in my admin panel works. Formatted HTML works fine in here too'
		] );
		//add more help tabs as needed with unique id's

		// Help sidebars are optional
		$screen->set_help_sidebar(
			'<p><strong>'.__( 'For more information:' ).'</strong></p>'.
			'<p><a href="http://wordpress.org/support/" target="_blank">'._( 'Support Forums' ).'</a></p>'
		);
	}

	/**
	 * @param string $wizardSlug
	 * @return string
	 * @uses nonce
	 */
	public function getUrl_Wizard( string $wizardSlug ) :string {
		$def = $this->getWizardDefinition( $wizardSlug );
		if ( empty( $def[ 'min_user_permissions' ] ) ) { // i.e. no login/minimum perms
			$url = Services::WpGeneral()->getHomeUrl();
		}
		else {
			$url = Services::WpGeneral()->getAdminUrl( 'admin.php' );
		}

		return add_query_arg(
			[
				'page'          => $this->getCon()->getModule_Insights()->getModSlug(),
				'inav'          => 'wizard',
				'shield_action' => 'wizard',
				'wizard'        => $wizardSlug,
				'nonwizard'     => wp_create_nonce( 'wizard'.$wizardSlug )
			],
			$url
		);
	}

	/**
	 * @return string
	 */
	public function getUrl_WizardLanding() {
		return $this->getUrl_Wizard( 'landing' );
	}

	/**
	 * @param string $wizardSlug
	 * @return array
	 */
	public function getWizardDefinition( string $wizardSlug ) {
		$def = null;
		if ( $this->hasWizardDefinition( $wizardSlug ) ) {
			$def = $this->getWizardDefinitions()[ $wizardSlug ];
		}
		return $def;
	}

	public function getWizardDefinitions() :array {
		return is_array( $this->getDef( 'wizards' ) ) ? $this->getDef( 'wizards' ) : [];
	}

	public function hasWizard() :bool {
		return count( $this->getWizardDefinitions() ) > 0;
	}

	public function hasWizardDefinition( string $wizardSlug ) :bool {
		return !empty( $this->getWizardDefinitions()[ $wizardSlug ] );
	}

	public function getIsShowMarketing() :bool {
		return (bool)apply_filters( $this->prefix( 'show_marketing' ), !$this->isPremium() );
	}

	/**
	 * @return string
	 */
	public function renderOptionsForm() {

		if ( $this->canDisplayOptionsForm() ) {
			$template = 'components/options_form/main.twig';
		}
		else {
			$template = 'subfeature-access_restricted';
		}

		try {
			return $this->getCon()
						->getRenderer()
						->setTemplate( $template )
						->setRenderVars( $this->getUIHandler()->getBaseDisplayData() )
						->setTemplateEngineTwig()
						->render();
		}
		catch ( \Exception $e ) {
			return 'Error rendering options form: '.$e->getMessage();
		}
	}

	public function canDisplayOptionsForm() :bool {
		return $this->getOptions()->isAccessRestricted() ? $this->getCon()->isPluginAdmin() : true;
	}

	public function getScriptLocalisations() :array {
		return [
			[
				'plugin',
				'icwp_wpsf_vars_base',
				[
					'ajax' => [
						'mod_options'          => $this->getAjaxActionData( 'mod_options' ),
						'mod_opts_form_render' => $this->getAjaxActionData( 'mod_opts_form_render' ),
					]
				]
			]
		];
	}

	public function getCustomScriptEnqueues() :array {
		return [];
	}

	/**
	 * @param array  $aData
	 * @param string $sSubView
	 */
	protected function display( $aData = [], $sSubView = '' ) {
	}

	/**
	 * @param array $aData
	 * @return string
	 * @throws \Exception
	 */
	public function renderAdminNotice( $aData ) {
		if ( empty( $aData[ 'notice_attributes' ] ) ) {
			throw new \Exception( 'notice_attributes is empty' );
		}

		if ( !isset( $aData[ 'icwp_admin_notice_template' ] ) ) {
			$aData[ 'icwp_admin_notice_template' ] = $aData[ 'notice_attributes' ][ 'notice_id' ];
		}

		if ( !isset( $aData[ 'notice_classes' ] ) ) {
			$aData[ 'notice_classes' ] = [];
		}
		if ( is_array( $aData[ 'notice_classes' ] ) ) {
			$aData[ 'notice_classes' ][] = $aData[ 'notice_attributes' ][ 'type' ];
			if ( empty( $aData[ 'notice_classes' ] )
				 || ( !in_array( 'error', $aData[ 'notice_classes' ] ) && !in_array( 'updated', $aData[ 'notice_classes' ] ) ) ) {
				$aData[ 'notice_classes' ][] = 'updated';
			}
		}
		$aData[ 'notice_classes' ] = implode( ' ', $aData[ 'notice_classes' ] );

		$aAjaxData = $this->getAjaxActionData( 'dismiss_admin_notice' );
		$aAjaxData[ 'hide' ] = 1;
		$aAjaxData[ 'notice_id' ] = $aData[ 'notice_attributes' ][ 'notice_id' ];
		$aData[ 'ajax' ][ 'dismiss_admin_notice' ] = json_encode( $aAjaxData );

		$bTwig = $aData[ 'notice_attributes' ][ 'twig' ];
		$sTemplate = $bTwig ? '/notices/'.$aAjaxData[ 'notice_id' ] : 'notices/admin-notice-template';
		return $this->renderTemplate( $sTemplate, $aData, $bTwig );
	}

	public function renderTemplate( string $template, array $data = [], bool $isTwig = false ) :string {
		if ( empty( $data[ 'unique_render_id' ] ) ) {
			$data[ 'unique_render_id' ] = 'noticeid-'.substr( md5( mt_rand() ), 0, 5 );
		}
		try {
			$oRndr = $this->getCon()->getRenderer();
			if ( $isTwig || preg_match( '#^.*\.twig$#i', $template ) ) {
				$oRndr->setTemplateEngineTwig();
			}

			$data[ 'strings' ] = Services::DataManipulation()
										 ->mergeArraysRecursive(
											 $this->getStrings()->getDisplayStrings(),
											 $data[ 'strings' ] ?? []
										 );

			$render = $oRndr->setTemplate( $template )
							->setRenderVars( $data )
							->render();
		}
		catch ( \Exception $e ) {
			$render = $e->getMessage();
			error_log( $e->getMessage() );
		}

		return (string)$render;
	}

	/**
	 * @param array $aTransferableOptions
	 * @return array
	 */
	public function exportTransferableOptions( $aTransferableOptions ) {
		if ( !is_array( $aTransferableOptions ) ) {
			$aTransferableOptions = [];
		}
		$aTransferableOptions[ $this->getOptionsStorageKey() ] = $this->getOptions()->getTransferableOptions();
		return $aTransferableOptions;
	}

	public function getMainWpData() :array {
		return [
			'options' => $this->getOptions()->getTransferableOptions()
		];
	}

	/**
	 * See plugin controller for the nature of $aData wpPrivacyExport()
	 * @param array  $aExportItems
	 * @param string $sEmail
	 * @param int    $nPage
	 * @return array
	 */
	public function onWpPrivacyExport( $aExportItems, $sEmail, $nPage = 1 ) {
		return $aExportItems;
	}

	/**
	 * See plugin controller for the nature of $aData wpPrivacyErase()
	 * @param array  $aData
	 * @param string $sEmail
	 * @param int    $nPage
	 * @return array
	 */
	public function onWpPrivacyErase( $aData, $sEmail, $nPage = 1 ) {
		return $aData;
	}

	/**
	 * @return null|Shield\Modules\Base\Options|mixed
	 */
	public function getOptions() {
		$opts = $this->opts ?? $this->oOpts;
		if ( !$opts instanceof Options ) {
			$con = $this->getCon();
			$this->opts = $this->loadModElement( 'Options' );
			$this->opts->setPathToConfig( $con->getPath_ConfigFile( $this->getSlug() ) )
					   ->setRebuildFromFile( $con->cfg->rebuilt )
					   ->setOptionsStorageKey( $this->getOptionsStorageKey() )
					   ->setIfLoadOptionsFromStorage( !$con->getIsResetPlugin() );
			$opts = $this->opts;
		}
		return $opts;
	}

	/**
	 * @return RestHandler|mixed
	 */
	public function getRestHandler() {
		return $this->loadModElement( 'RestHandler' );
	}

	/**
	 * @return AdminPage
	 */
	public function getAdminPage() {
		if ( !isset( $this->adminPage ) ) {
			$this->adminPage = $this->loadModElement( 'AdminPage' );
		}
		return $this->adminPage;
	}

	/**
	 * @return Shield\Modules\Base\WpCli
	 * @throws \Exception
	 */
	public function getWpCli() {
		if ( !isset( $this->oWpCli ) ) {
			$this->oWpCli = $this->loadModElement( 'WpCli' );
			if ( !$this->oWpCli instanceof Shield\Modules\Base\WpCli ) {
				throw new \Exception( 'WP-CLI not supported' );
			}
		}
		return $this->oWpCli;
	}

	/**
	 * @return null|Shield\Modules\Base\Strings
	 */
	public function getStrings() {
		return $this->loadStrings()->setMod( $this );
	}

	/**
	 * @return Shield\Modules\Base\UI
	 */
	public function getUIHandler() {
		if ( !isset( $this->oUI ) ) {
			$this->oUI = $this->loadModElement( 'UI' );
			if ( !$this->oUI instanceof Shield\Modules\Base\UI ) {
				// TODO: autoloader for base classes
				$this->oUI = $this->loadModElement( 'ShieldUI' );
			}
		}
		return $this->oUI;
	}

	/**
	 * @return Shield\Modules\Base\Reporting|mixed|false
	 */
	public function getReportingHandler() {
		if ( !isset( $this->oReporting ) ) {
			$this->oReporting = $this->loadModElement( 'Reporting' );
		}
		return $this->oReporting;
	}

	protected function loadAdminNotices() {
		$N = $this->loadModElement( 'AdminNotices' );
		if ( $N instanceof Shield\Modules\Base\AdminNotices ) {
			$N->run();
		}
	}

	protected function loadAjaxHandler() {
		$this->loadModElement( 'AjaxHandler' );
	}

	protected function loadDebug() {
		$req = Services::Request();
		if ( $req->query( 'debug' ) && $req->query( 'mod' ) == $this->getModSlug()
			 && $this->getCon()->isPluginAdmin() ) {
			/** @var Shield\Modules\Base\Debug $debug */
			$debug = $this->loadModElement( 'Debug' );
			$debug->run();
		}
	}

	/**
	 * @return Shield\Modules\Base\Strings|mixed
	 */
	protected function loadStrings() {
		return $this->loadModElement( 'Strings' );
	}

	/**
	 * @param string $class
	 * @return false|Shield\Modules\ModConsumer
	 */
	private function loadModElement( string $class ) {
		$element = false;
		try {
			$C = $this->findElementClass( $class, true );
			/** @var Shield\Modules\ModConsumer $element */
			$element = @class_exists( $C ) ? new $C() : false;
			if ( method_exists( $element, 'setMod' ) ) {
				$element->setMod( $this );
			}
		}
		catch ( \Exception $e ) {
		}
		return $element;
	}

	/**
	 * @param string $element
	 * @param bool   $bThrowException
	 * @return string|null
	 * @throws \Exception
	 */
	protected function findElementClass( string $element, $bThrowException = true ) {
		$theClass = null;

		$roots = array_map( function ( $root ) {
			return rtrim( $root, '\\' ).'\\';
		}, $this->getNamespaceRoots() );

		foreach ( $roots as $NS ) {
			$maybe = $NS.$element;
			if ( @class_exists( $maybe ) ) {
				if ( ( new \ReflectionClass( $maybe ) )->isInstantiable() ) {
					$theClass = $maybe;
					break;
				}
			}
		}

		if ( $bThrowException && is_null( $theClass ) ) {
			throw new \Exception( sprintf( 'Could not find class for element "%s".', $element ) );
		}
		return $theClass;
	}

	protected function getBaseNamespace() {
		return __NAMESPACE__;
	}

	protected function getNamespace() :string {
		return ( new \ReflectionClass( $this ) )->getNamespaceName();
	}

	protected function getNamespaceRoots() :array {
		return [
			$this->getNamespace(),
			$this->getBaseNamespace()
		];
	}

	/**
	 * Saves the options to the WordPress Options store.
	 * @return void
	 * @deprecated 8.4
	 */
	public function savePluginOptions() {
		$this->saveModOptions();
	}
}