Source: includes/class-modules.php

<?php
namespace jb;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class Modules
 *
 * @package jb
 *
 * @since 1.2.1
 */
class Modules {

	/**
	 * Modules list
	 *
	 * @var array
	 */
	private $list = array();

	/**
	 * Modules constructor.
	 */
	public function __construct() {
		add_action( 'jb_core_loaded', array( &$this, 'predefined_modules' ), 0 );
	}

	/**
	 * Set modules list
	 *
	 * @usedby on `jb_core_loaded` hook for modules initialization
	 *
	 * @uses get_plugins() for getting installed plugins list
	 * @uses DIRECTORY_SEPARATOR for getting proper path to modules' directories
	 */
	public function predefined_modules() {
		$modules = JB()->config()->get( 'modules' );
		/**
		 * Filters predefined modules list.
		 *
		 * Note: It's the filter for getting the modules list, but not validate the module structure and necessary files yet!
		 *
		 * @since 1.2.2
		 * @hook jb_predefined_modules
		 *
		 * @param {array} $modules Modules list. See structure inside class-config.php file.
		 *
		 * @return {array} Modules list.
		 */
		$modules = apply_filters( 'jb_predefined_modules', $modules );

		/** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
		$all_plugins = apply_filters( 'all_plugins', get_plugins() );

		$active_plugins = (array) get_option( 'active_plugins', array() );

		if ( is_multisite() ) {
			$active_plugins = array_merge( $active_plugins, get_site_option( 'active_sitewide_plugins', array() ) );
		}

		foreach ( $modules as &$data ) {
			// @todo checking the proper module structure function if not proper make 'invalid' data with displaying red line in list table

			// check the module's dir
			if ( ! is_dir( $data['path'] ) ) {

				$data['disabled']    = true;
				$data['description'] = '<strong>' . __( 'Module has not been installed properly. Please check the module\'s directory and re-install it.', 'jobboardwp' ) . '</strong><br />' . $data['description'];

			} elseif ( array_key_exists( 'plugins_required', $data ) ) {
				$maybe_installed = array_intersect( array_keys( $data['plugins_required'] ), array_keys( $all_plugins ) );
				$not_installed   = array_diff( array_keys( $data['plugins_required'] ), $maybe_installed );

				$data['disabled'] = count( $not_installed ) > 0;

				if ( $data['disabled'] ) {
					$plugins_titles = array();
					foreach ( $not_installed as $plugin_slug ) {
						$plugins_titles[] = '<a href="' . esc_url( $data['plugins_required'][ $plugin_slug ]['url'] ) . '" target="_blank">' . esc_html( $data['plugins_required'][ $plugin_slug ]['name'] ) . '</a>';
					}
					$plugins_titles = '"' . implode( '", "', $plugins_titles ) . '"';
					/* translators: %s: activate notice */
					$data['description'] = '<strong>' . sprintf( _n( 'Module cannot be activated until %s plugin is installed and activated.', 'Module cannot be activated until %s plugins are installed and activated.', count( $not_installed ), 'jobboardwp' ), $plugins_titles ) . '</strong><br />' . $data['description'];
				} else {
					$maybe_activated = array_intersect( array_keys( $data['plugins_required'] ), $active_plugins );
					$not_active      = array_diff( array_keys( $data['plugins_required'] ), $maybe_activated );

					$data['disabled'] = count( $not_active ) > 0;
					if ( $data['disabled'] ) {
						$plugins_titles = array();
						foreach ( $not_active as $plugin_slug ) {
							$plugins_titles[] = '<a href="' . esc_url( $data['plugins_required'][ $plugin_slug ]['url'] ) . '" target="_blank">' . esc_html( $data['plugins_required'][ $plugin_slug ]['name'] ) . '</a>';
						}
						$plugins_titles = '"' . implode( '", "', $plugins_titles ) . '"';
						/* translators: %s: activate notice */
						$data['description'] = '<strong>' . sprintf( _n( 'Module cannot be activated until %s plugin is activated.', 'Module cannot be activated until %s plugins are activated.', count( $not_active ), 'jobboardwp' ), $plugins_titles ) . '</strong><br />' . $data['description'];
					}
				}
			}

			// set `disabled = false` by default
			if ( ! array_key_exists( 'disabled', $data ) ) {
				$data['disabled'] = false;
			}
		}
		unset( $data );
		/**
		 * Filters already validated modules list.
		 *
		 * Note: It's the filter for getting the modules list. There are only validated structure and enabled for activation modules.
		 *       You may use this filter for adding additional validation for modules.
		 *
		 * @since 1.2.2
		 * @hook jb_predefined_validated_modules
		 *
		 * @param {array} $modules Modules list.
		 *
		 * @return {array} Modules list.
		 */
		$this->list = apply_filters( 'jb_predefined_validated_modules', $modules );
	}

	/**
	 * Get list of modules
	 *
	 * @uses list
	 *
	 * @return array
	 */
	public function get_list() {
		/**
		 * Filters already formatted modules list.
		 *
		 * Note: It's the filter for getting the formatted modules list.
		 *
		 * @since 1.2.2
		 * @hook jb_formatting_modules_list
		 *
		 * @param {array} $modules Modules list.
		 *
		 * @return {array} Modules list.
		 */
		return apply_filters( 'jb_formatting_modules_list', $this->list );
	}

	/**
	 * Get module data
	 *
	 * @param string $slug Module slug
	 *
	 * @return bool|array Returns `false` if module doesn't exists
	 *
	 * @uses exists
	 */
	public function get_data( $slug ) {
		if ( ! $this->exists( $slug ) ) {
			return false;
		}

		return $this->list[ $slug ];
	}

	/**
	 * Checking if module exists
	 *
	 * @param string $slug Module slug
	 *
	 * @return bool Returns `false` if module doesn't exists, otherwise `true`
	 */
	public function exists( $slug ) {
		return array_key_exists( $slug, $this->list );
	}

	/**
	 * Check if module is active
	 *
	 * @param string $slug Module slug
	 *
	 *
	 * @uses exists
	 * @uses is_disabled
	 * @uses JB::undash()
	 * @uses JB::options()
	 *
	 * @return bool
	 */
	public function is_active( $slug ) {
		if ( ! $this->exists( $slug ) ) {
			return false;
		}

		if ( $this->is_disabled( $slug ) ) {
			return false;
		}

		$slug      = JB()->undash( $slug );
		$is_active = JB()->options()->get( "module_{$slug}_on" );

		return ! empty( $is_active );
	}

	/**
	 * Check if module is disabled
	 *
	 * @param string $slug Module slug
	 *
	 * @uses exists
	 * @uses get_data
	 *
	 * @return bool
	 */
	public function is_disabled( $slug ) {
		if ( ! $this->exists( $slug ) ) {
			return false;
		}

		$data = $this->get_data( $slug );
		return ! empty( $data['disabled'] );
	}

	/**
	 * Check if current user can activate a module
	 *
	 * @param string $slug Module slug
	 *
	 * @uses exists
	 * @uses is_disabled
	 * @uses is_active
	 *
	 * @return bool
	 */
	public function can_activate( $slug ) {
		if ( ! current_user_can( 'manage_options' ) ) {
			return false;
		}

		if ( ! $this->exists( $slug ) ) {
			return false;
		}

		if ( $this->is_disabled( $slug ) ) {
			return false;
		}

		if ( $this->is_active( $slug ) ) {
			return false;
		}

		/**
		 * Filters the marker to check if the module can be activated.
		 *
		 * @since 1.2.2
		 * @hook jb_module_can_activate
		 *
		 * @param {bool}   $can_activate Marker for checking if module can be activated. It's `true` by default.
		 * @param {string} $slug Module slug.
		 *
		 * @return {bool} `true` if a module can be activated.
		 */
		return apply_filters( 'jb_module_can_activate', true, $slug );
	}

	/**
	 * Checking if current user can deactivate a module
	 *
	 * @param string $slug Module slug
	 *
	 * @uses exists
	 * @uses is_disabled
	 * @uses is_active
	 *
	 * @return bool
	 */
	public function can_deactivate( $slug ) {
		if ( ! current_user_can( 'manage_options' ) ) {
			return false;
		}

		if ( ! $this->exists( $slug ) ) {
			return false;
		}

		if ( $this->is_disabled( $slug ) ) {
			return false;
		}

		if ( ! $this->is_active( $slug ) ) {
			return false;
		}

		return true;
	}

	/**
	 * Checking if current user can flush module's data
	 *
	 * @param string $slug Module slug
	 *
	 * @uses exists
	 * @uses is_disabled
	 * @uses is_active
	 * @uses JB::undash()
	 * @uses JB::options()
	 *
	 * @return bool
	 */
	public function can_flush( $slug ) {
		if ( ! current_user_can( 'manage_options' ) ) {
			return false;
		}

		if ( ! $this->exists( $slug ) ) {
			return false;
		}

		if ( $this->is_disabled( $slug ) ) {
			return false;
		}

		if ( $this->is_active( $slug ) ) {
			return false;
		}

		if ( ! $this->is_first_installed( $slug ) ) {
			return false;
		}

		return true;
	}

	/**
	 * Checking if the module had been already first-time activated
	 * Must be reset this marker after flushing data of the module
	 *
	 * @param string $slug
	 *
	 * @return bool
	 */
	public function is_first_installed( $slug ) {
		$slug             = JB()->undash( $slug );
		$first_activation = JB()->options()->get( "module_{$slug}_first_activation" );

		return ! empty( $first_activation );
	}

	/**
	 * Checking if the module has the settings section in wp-admin dashboard
	 *
	 * @param string $slug
	 *
	 * @return bool
	 */
	public function has_settings_section( $slug ) {
		return ! empty( JB()->admin()->settings()->settings_structure['modules']['sections'][ $slug ] );
	}

	/**
	 * Module's activation handler
	 *
	 * @param string $slug Module's slug
	 *
	 * @uses can_activate
	 * @uses install::start()
	 * @uses JB::undash()
	 * @uses JB::options()
	 *
	 * @return bool
	 */
	public function activate( $slug ) {
		if ( ! $this->can_activate( $slug ) ) {
			return false;
		}

		$this->install( $slug )->start();

		$slug = JB()->undash( $slug );

		JB()->options()->update( "module_{$slug}_on", true );

		$first_activation = JB()->options()->get( "module_{$slug}_first_activation" );
		if ( empty( $first_activation ) ) {
			JB()->options()->update( "module_{$slug}_first_activation", time() );
		}

		return true;
	}

	/**
	 * Module's deactivation handler
	 *
	 * @param string $slug Module slug
	 *
	 * @uses can_deactivate
	 * @uses JB::undash()
	 * @uses JB::options()
	 *
	 * @return bool
	 */
	public function deactivate( $slug ) {
		if ( ! $this->can_deactivate( $slug ) ) {
			return false;
		}

		$slug = JB()->undash( $slug );

		JB()->options()->update( "module_{$slug}_on", false );

		return true;
	}

	/**
	 * Module's flushing data handler
	 *
	 * @param string $slug Module slug
	 *
	 * @uses can_flush
	 * @uses get_data
	 * @uses JB::undash()
	 * @uses JB::options()
	 *
	 * @return bool
	 */
	public function flush_data( $slug ) {
		if ( ! $this->can_flush( $slug ) ) {
			return false;
		}

		$data = $this->get_data( $slug );

		$slug = JB()->undash( $slug );
		JB()->options()->delete( "module_{$slug}_first_activation" );

		$uninstall_path = $data['path'] . DIRECTORY_SEPARATOR . 'uninstall.php';
		if ( file_exists( $uninstall_path ) ) {
			include_once $uninstall_path;
		}

		return true;
	}

	/**
	 * Load all modules
	 *
	 * @uses get_list
	 * @uses is_active
	 * @uses run
	 */
	public function load_modules() {
		$modules = $this->get_list();
		if ( empty( $modules ) ) {
			return;
		}

		foreach ( $modules as $slug => $data ) {
			if ( ! $this->is_active( $slug ) ) {
				continue;
			}

			$this->run( $slug );
		}
	}

	/**
	 * Run main class of module
	 *
	 * @param string $slug Module slug
	 *
	 * @uses JB::undash()
	 * @uses JB::call_class()
	 */
	private function run( $slug ) {
		$slug = JB()->undash( $slug );
		JB()->call_class( "jbm\\{$slug}\\Init" );
	}

	/**
	 * Installation handler for single module
	 *
	 * @param string $slug Module slug
	 *
	 * @uses JB::undash()
	 * @uses JB::call_class()
	 *
	 * @return mixed
	 */
	private function install( $slug ) {
		$slug = JB()->undash( $slug );
		return JB()->call_class( "jbm\\{$slug}\\Install" );
	}
}