Skip to content

Commit 21ff632

Browse files
committed
Initial commit
0 parents  commit 21ff632

File tree

2 files changed

+206
-0
lines changed

2 files changed

+206
-0
lines changed

kntnt-mu-scripts.php

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
<?php
2+
/**
3+
* Plugin Name: Kntnt Must Use Scripts
4+
* Description: Copies PHP files with namespace Kntnt\Mu_Scripts\* from scripts subfolder to mu-plugins directory on activation, removes them on deactivation.
5+
* Version: 1.0.0
6+
* Plugin URI: https://www.kntnt.com/
7+
* Author: Kntnt
8+
* Author URI: https://www.kntnt.com/
9+
* Requires at least: 6.0
10+
* Requires PHP: 8.3
11+
* License: GPL v2 or later
12+
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
13+
*/
14+
15+
declare( strict_types = 1 );
16+
17+
namespace Kntnt\Mu_Scripts;
18+
19+
if ( ! defined( 'ABSPATH' ) ) {
20+
exit;
21+
}
22+
23+
enum LogLevel: string {
24+
25+
case INFO = 'info';
26+
case ERROR = 'error';
27+
case WARNING = 'warning';
28+
29+
}
30+
31+
class Plugin {
32+
33+
/**
34+
* Required namespace prefix for mu-plugin files
35+
*/
36+
private const string REQUIRED_NAMESPACE_PREFIX = 'Kntnt\\Mu_Scripts\\';
37+
38+
/**
39+
* Plugin file path
40+
*/
41+
private static string $plugin_file;
42+
43+
/**
44+
* Initialize the plugin
45+
*/
46+
public static function init(): void {
47+
self::$plugin_file = __FILE__;
48+
49+
// Register activation and deactivation hooks
50+
register_activation_hook( self::$plugin_file, self::on_activation( ... ) );
51+
register_deactivation_hook( self::$plugin_file, self::on_deactivation( ... ) );
52+
}
53+
54+
/**
55+
* Plugin activation callback
56+
*/
57+
public static function on_activation(): void {
58+
$plugin_dir = plugin_dir_path( self::$plugin_file );
59+
$scripts_dir = $plugin_dir . 'scripts/';
60+
61+
if ( ! is_dir( $scripts_dir ) ) {
62+
self::log( LogLevel::ERROR, "Scripts directory not found: {$scripts_dir}" );
63+
return;
64+
}
65+
66+
$mu_plugins_dir = WPMU_PLUGIN_DIR . '/';
67+
68+
// Create mu-plugins directory if it doesn't exist
69+
// Note: We create it but never delete it, even on deactivation
70+
if ( ! is_dir( $mu_plugins_dir ) && ! wp_mkdir_p( $mu_plugins_dir ) ) {
71+
self::log( LogLevel::ERROR, "Failed to create mu-plugins directory: {$mu_plugins_dir}" );
72+
return;
73+
}
74+
75+
$copied_files = [];
76+
77+
try {
78+
$php_files = self::get_php_files( $scripts_dir );
79+
80+
foreach ( $php_files as $source_file ) {
81+
// Check if file has the required namespace
82+
if ( ! self::has_required_namespace( $source_file ) ) {
83+
$filename = basename( $source_file );
84+
self::log( LogLevel::WARNING, "Skipping {$filename} - missing required namespace " . self::REQUIRED_NAMESPACE_PREFIX );
85+
continue;
86+
}
87+
88+
$filename = basename( $source_file );
89+
$destination_file = $mu_plugins_dir . $filename;
90+
91+
if ( copy( $source_file, $destination_file ) ) {
92+
$copied_files[] = $filename;
93+
self::log( LogLevel::INFO, "Copied {$filename} to mu-plugins" );
94+
}
95+
else {
96+
self::log( LogLevel::ERROR, "Failed to copy {$filename}" );
97+
}
98+
}
99+
100+
$count = count( $copied_files );
101+
if ( $count > 0 ) {
102+
self::log( LogLevel::INFO, "Successfully copied {$count} PHP files to mu-plugins" );
103+
}
104+
105+
} catch ( Exception $e ) {
106+
self::log( LogLevel::ERROR, "Error during file copying: {$e->getMessage()}" );
107+
}
108+
}
109+
110+
/**
111+
* Plugin deactivation callback
112+
*/
113+
public static function on_deactivation(): void {
114+
$mu_plugins_dir = WPMU_PLUGIN_DIR . '/';
115+
116+
if ( ! is_dir( $mu_plugins_dir ) ) {
117+
self::log( LogLevel::INFO, 'No mu-plugins directory found' );
118+
return;
119+
}
120+
121+
$removed_count = 0;
122+
$php_files = self::get_php_files( $mu_plugins_dir );
123+
124+
foreach ( $php_files as $file_path ) {
125+
if ( self::has_required_namespace( $file_path ) ) {
126+
$filename = basename( $file_path );
127+
128+
if ( unlink( $file_path ) ) {
129+
$removed_count ++;
130+
self::log( LogLevel::INFO, "Removed {$filename} from mu-plugins" );
131+
}
132+
else {
133+
self::log( LogLevel::ERROR, "Failed to remove {$filename}" );
134+
}
135+
}
136+
}
137+
138+
self::log( LogLevel::INFO, "Successfully removed {$removed_count} files from mu-plugins" );
139+
}
140+
141+
/**
142+
* Get all PHP files from a directory (root level only)
143+
*/
144+
private static function get_php_files( string $directory ): array {
145+
if ( ! is_dir( $directory ) ) {
146+
return [];
147+
}
148+
149+
$php_files = [];
150+
$files = scandir( $directory );
151+
152+
foreach ( $files as $file ) {
153+
if ( $file === '.' || $file === '..' ) {
154+
continue;
155+
}
156+
157+
$file_path = $directory . $file;
158+
if ( is_file( $file_path ) && pathinfo( $file, PATHINFO_EXTENSION ) === 'php' ) {
159+
$php_files[] = $file_path;
160+
}
161+
}
162+
163+
return $php_files;
164+
}
165+
166+
/**
167+
* Check if a PHP file has the required namespace
168+
* Only accepts format: namespace Kntnt\Mu_Scripts\Something;
169+
*/
170+
private static function has_required_namespace( string $file_path ): bool {
171+
// Read first 2KB of file (namespace should be early in file)
172+
$content = file_get_contents( $file_path, false, null, 0, 2048 );
173+
if ( $content === false ) {
174+
return false;
175+
}
176+
177+
// Remove comments to avoid false matches
178+
$content = preg_replace( [
179+
'/\/\*.*?\*\//s', // Block comments
180+
'/\/\/.*$/m', // Line comments
181+
'/#.*$/m', // Hash comments
182+
], '', $content );
183+
184+
// Look for our required namespace declaration
185+
$pattern = '/^\s*namespace\s+' . preg_quote( self::REQUIRED_NAMESPACE_PREFIX, '/' ) . '/m';
186+
return preg_match( $pattern, $content ) === 1;
187+
}
188+
189+
/**
190+
* Log messages with different levels
191+
*/
192+
private static function log( LogLevel $level, string $message ): void {
193+
$prefix = match ( $level ) {
194+
LogLevel::INFO => 'Kntnt Must Use Scripts',
195+
LogLevel::ERROR => 'Kntnt Must Use Scripts ERROR',
196+
LogLevel::WARNING => 'Kntnt Must Use Scripts WARNING',
197+
};
198+
199+
error_log( "{$prefix}: {$message}" );
200+
}
201+
202+
}
203+
204+
// Initialize the plugin
205+
Plugin::init();

scripts/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
!.gitignore

0 commit comments

Comments
 (0)