Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add module loader #8

Merged
merged 9 commits into from
Mar 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "publisher-name/project-name",
"name": "publisher-name/custom-plugin",
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "*",
"squizlabs/php_codesniffer": "^3.6",
Expand All @@ -10,11 +10,20 @@
"scripts": {
"lint": "./vendor/bin/phpcs --standard=phpcs.xml"
},
"autoload": {
"psr-4": {
"PublisherName\\": "inc/"
},
"files": []
},
"config": {
"allow-plugins": {
"squizlabs/php_codesniffer": true,
"dealerdirect/phpcodesniffer-composer-installer": true,
"wp-coding-standards/wpcs": true
}
}
"platform": {
"php": "8.0"
},
"allow-plugins": {
"squizlabs/php_codesniffer": true,
"dealerdirect/phpcodesniffer-composer-installer": true,
"wp-coding-standards/wpcs": true
}
}
}
Empty file removed inc/.gitkeep
Empty file.
159 changes: 159 additions & 0 deletions inc/Module_Loader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
<?php
/**
* Module Loader
*
* @package PublisherName
*/

namespace PublisherName;

defined( 'ABSPATH' ) || exit;

/**
* Module Loader
*/
class Module_Loader {

const MODULES_OPTION_NAME = __NAMESPACE__ . '-modules';

/**
* Initialize the class
*
* @return void
*/
public static function init() {

add_action( 'admin_menu', [ __CLASS__, 'add_admin_menu' ] );
add_filter( 'allowed_options', [ __CLASS__, 'add_allowed_options' ] );
add_action( 'plugins_loaded', [ __CLASS__, 'load_modules' ] );
}

/**
* Add the admin menu.
*
* @return void
*/
public static function add_admin_menu() {
add_options_page(
'Custom Modules',
'Custom Modules',
'manage_options',
'custom-modules',
[ __CLASS__, 'render_admin_page' ]
);
}

/**
* Add the custom modules to the allowed options.
*
* @param array $options The allowed options.
* @return array
*/
public static function add_allowed_options( $options ) {
$options['custom-modules'] = [ self::MODULES_OPTION_NAME ];
return $options;
}

/**
* Render the admin page.
*
* @return void
*/
public static function render_admin_page() {
$current_active_modules = get_option( self::MODULES_OPTION_NAME );
if ( ! is_array( $current_active_modules ) ) {
$current_active_modules = [];
}
$available_modules = self::get_available_modules();
?>
<div class="wrap">
<h1>Custom Modules</h1>
<p>Enable or disable Custom modules</p>
<form method="post" action="options.php">
<?php wp_nonce_field( 'custom-modules-options' ); ?>
<input type="hidden" name="option_page" value="custom-modules">
<input type="hidden" name="action" value="update">
<table class="form-table" role="presentation">
<tbody>
<?php foreach ( $available_modules as $module ) : ?>
<?php $checked = in_array( $module['path'], $current_active_modules, true ) ? 'checked' : ''; ?>
<tr>
<th scope="row"><?php echo esc_html( $module['name'] ); ?></th>
<td>
<fieldset>
<legend class="screen-reader-text">
<span><?php echo esc_html( $module['name'] ); ?></span>
</legend>
<label for="<?php echo esc_attr( $module['slug'] ); ?>">
<input name="<?php echo esc_attr( self::MODULES_OPTION_NAME ); ?>[]" type="checkbox" value="<?php echo esc_attr( $module['path'] ); ?>" <?php echo esc_attr( $checked ); ?>>
Enabled
</label>
<p class="description">
<?php echo esc_html( $module['description'] ); ?>
</p>
</fieldset>
</td>
</tr>
<?php endforeach; ?>
<tbody>

</table>
<?php submit_button(); ?>
</form>
</div>
<?php
}

/**
* Get the available modules in the file system.
*
* @return array
*/
private static function get_available_modules() {
$modules_dir = __DIR__ . '/Modules';
$dirs = scandir( $modules_dir );
$modules = [];
foreach ( $dirs as $dir ) {
if ( '.' === $dir || '..' === $dir ) {
continue;
}
$module_file = $modules_dir . '/' . $dir . '/module.php';
if ( file_exists( $module_file ) ) {
$modules[ $dir ] = [
'slug' => $dir,
'path' => $module_file,
'name' => $dir,
'description' => '',
];
}

$info_file = $modules_dir . '/' . $dir . '/info.json';
if ( file_exists( $info_file ) ) {
$info = json_decode( file_get_contents( $info_file ), true ); // phpcs:ignore WordPressVIPMinimum.Performance.FetchingRemoteData.FileGetContentsUnknown
if ( ! empty( $info['name'] ) ) {
$modules[ $dir ]['name'] = $info['name'];
}
if ( ! empty( $info['description'] ) ) {
$modules[ $dir ]['description'] = $info['description'];
}
}
}
return $modules;
}

/**
* Load the active modules.
*
* @return void
*/
public static function load_modules() {
$active_modules = get_option( self::MODULES_OPTION_NAME, [] );
if ( ! empty( $active_modules ) ) {
foreach ( $active_modules as $module ) {
if ( file_exists( $module ) ) {
require_once $module;
}
}
}
}
}
39 changes: 39 additions & 0 deletions inc/Modules/Sample/Admin_Panel_Notice.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php
/**
* Admin Panel Notice.
*
* @package PublisherName
*/

namespace PublisherName\Modules\Sample;

defined( 'ABSPATH' ) || exit;

/**
* Class to add a notice to the admin panel
*/
class Admin_Panel_Notice {

/**
* Initialize the class
*
* @return void
*/
public static function init() {
add_action( 'admin_bar_menu', [ __CLASS__, 'add_notice' ], 999 );
}

/**
* Adds a notice to the admin bar
*
* @param object $wp_admin_bar The Admin bar object.
* @return void
*/
public static function add_notice( $wp_admin_bar ) {
$args = array(
'id' => 'sample_info',
'title' => 'Custom Module is enabled',
);
$wp_admin_bar->add_node( $args );
}
}
4 changes: 4 additions & 0 deletions inc/Modules/Sample/info.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "Sample Module",
"description": "This module only adds a note at the admin top bar. It exists only as an example. Delete it and create your own modules."
}
18 changes: 18 additions & 0 deletions inc/Modules/Sample/module.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php
/**
* This file will be automatically loaded when the module is active.
*
* @package PublisherName
*/

namespace PublisherName\Modules\Sample;

/**
* Here you can have code doing stuff. Just remember to always perform actions in a hook callback and never in the global scope.
*/

/**
* You don't need to use classes if you don't want to, but this is an
* example of how classes files are automatically loaded if you follow the naming convention.
*/
Admin_Panel_Notice::init();
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "starter-plugin",
"name": "publisher-name",
"version": "1.0.0",
"description": "A starting point for custom plugins on the Newspack platform",
"author": "Publisher name",
Expand Down Expand Up @@ -32,4 +32,4 @@
"husky": "^9.1.7",
"lint-staged": "^13.2.0"
}
}
}
3 changes: 3 additions & 0 deletions phpcs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
<exclude name="Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed" />
<exclude name="Universal.NamingConventions.NoReservedKeywordParameterNames" />
<exclude name="Generic.CodeAnalysis.UnusedFunctionParameter.Found" />
<!-- Allow for PSR-4 file names-->
<exclude name="WordPress.Files.FileName.NotHyphenatedLowercase" />
<exclude name="WordPress.Files.FileName.InvalidClassFileName" />
</rule>

<rule ref="PHPCompatibilityWP"/>
Expand Down
23 changes: 23 additions & 0 deletions plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,26 @@

// Exit if accessed directly.
defined( 'ABSPATH' ) || exit;

// This will load composer's autoload.
// Keep in mind that you need to run `composer dump-autoload` after adding new classes.
if ( file_exists( __DIR__ . '/vendor/autoload.php' ) ) {
require_once __DIR__ . '/vendor/autoload.php';
} else {
// Add admin notice in case the plugin has not been built.
add_action(
'admin_notices',
function() {
?>
<div class="notice notice-error">
<p><?php esc_html_e( 'Publisher Name plugin was not properly built.', 'publisher-name' ); ?></p>
</div>
<?php
}
);
return;
}

// Initialize the plugin.

Module_Loader::init();