Browse Source

Use string signature matching in file instead of using include
Encoded files will force exit the script causing maintenance script to exit prematurely. This update will search for the matching function signature using a standard str_contains().

Tim Fry 2 months ago
parent
commit
cb3c96f4e9
4 changed files with 112 additions and 26 deletions
  1. 1 1
      app_config.php
  2. 8 8
      maintenance.php
  3. 2 2
      readme.md
  4. 101 15
      resources/classes/maintenance.php

+ 1 - 1
app_config.php

@@ -33,7 +33,7 @@
 	$apps[$x]['subcategory'] = "";
 	$apps[$x]['version'] = "1.0";
 	$apps[$x]['license'] = "Mozilla Public License 1.1";
-	$apps[$x]['url'] = "http://www.fusionpbx.com";
+	$apps[$x]['url'] = "https://www.fusionpbx.com";
 	$apps[$x]['description']['en-us'] = "Performs maintenance work on database and filesystem";
 	$apps[$x]['description']['en-gb'] = "Performs maintenance work on database and filesystem";
 	$apps[$x]['description']['ar-eg'] = "يقوم بأعمال الصيانة على قاعدة البيانات ونظام الملفات";

+ 8 - 8
maintenance.php

@@ -44,6 +44,10 @@ if (!empty($_REQUEST['search'])) {
 	$search = '';
 }
 
+//set the domain and user
+$domain_uuid = $_SESSION['domain_uuid'] ?? '';
+$user_uuid = $_SESSION['user_uuid'] ?? '';
+
 //internationalization
 $language = new text;
 $text = $language->get();
@@ -80,11 +84,7 @@ if (!empty($_REQUEST['action'])) {
 }
 
 //create a boolean value to represent if show_all is enabled
-if (!empty($_REQUEST['show'])) {
-	$show_all = ($_REQUEST['show'] == 'all' && permission_exists('maintenance_show_all')) ? true : false;
-} else {
-	$show_all = false;
-}
+	$show_all = !empty($_REQUEST['show']) && $_REQUEST['show'] === 'all' && permission_exists('maintenance_show_all');
 
 //order by
 if (!empty($_REQUEST['order_by'])) {
@@ -102,7 +102,7 @@ if (!empty($_REQUEST['page'])) {
 	$page = '';
 }
 
-//load the settings
+//load the global settings only
 $default_settings = new settings(['database' => $database]);
 
 //get the list in the default settings
@@ -171,7 +171,7 @@ ksort($maintenance_apps);
 $url_params = '';
 
 if ($show_all) {
-	$url_params = (empty($url_params) ? '?' : '&') . 'show=all';
+	$url_params = '?show=all';
 }
 if (!empty($page)) {
 	$url_params .= (empty($url_params) ? '?' : '&') . 'page=' . $page;
@@ -238,7 +238,7 @@ echo "				</tr>";
 
 //list all maintenance applications from the defaults settings for global and each domain and show if they are enabled or disabled
 foreach ($maintenance_apps as $class => $app_settings) {
-	//make the class name more user friendly
+	//make the class name more user-friendly
 	if ($class === 'cdr') {
 	    $display_name = strtoupper(str_replace('_', ' ', $class));
 	} else {

+ 2 - 2
readme.md

@@ -3,10 +3,10 @@
 ## Why Use Maintenance Tasks?
 This application fully integrates in to the Dashboard to show enabled / disabled maintenance applications at a glance on the current domain.
 - Automatic detection and installation of new maintenance services when **App Defaults** is executed.
-- Each application can execute on a per domain basis making it possible for a per tenant limit
+- Each application can execute on a per-domain basis making it possible for a per-tenant limit
 - Simple function call for each class enabling a complete customization for the maintenance application. This opens a new host of capabilities like filesystem quotas or archiving old database records.
 - Built-in logging is available via the *maintenance_service::log_write()* method and viewable in the new **Maintenance Logs** viewer (see screenshots).
-- Default setings are done automatically per maintenance application when registered. Each application can set the number of days to retain data both globally and per domain under the **Maintenance** category.
+- Default settings are done automatically per maintenance application when registered. Each application can set the number of days to retain data both globally and per domain under the **Maintenance** category.
 
 ## New Service Class
 Utilizes the new service class to allow for easy installation and a standardize comprehensive command line interpreter. With the ability to reload the maintenance service settings without a restart of the service.

+ 101 - 15
resources/classes/maintenance.php

@@ -46,6 +46,7 @@ class maintenance {
 	const FILESYSTEM_SUBCATEGORY = 'filesystem_retention_days';
 
 	private static $app_config_list = null;
+	static $classes = [];
 
 	/**
 	 * Returns an array of domain names with their domain UUID as the array key
@@ -362,30 +363,115 @@ class maintenance {
 	 * @return array Names of classes or empty array if none found
 	 */
 	public static function find_classes_by_method(string $method_name): array {
-		//set defaults
 		$found_classes = [];
-		$project_root = dirname(__DIR__, 4);
+		$class_files = self::get_classes();
 
-		//get the autoloader
-		if (!class_exists('auto_loader')) {
-			require_once $project_root . '/resources/classes/auto_loader.php';
-			new auto_loader();
+		//iterate over the files
+		foreach ($class_files as $file) {
+			$contents = file_get_contents($file);
+			if (str_contains($contents, 'public static function ' . $method_name . '(settings $settings): void {')) {
+				$class = basename($file, '.php');
+				$found_classes[$class] = $file;
+			}
+		}
+		return $found_classes;
+	}
+
+	private static function get_classes() {
+		//set project path using magic dir constant
+		$project_path = dirname(__DIR__, 4);
+
+		//build the array of all locations for classes in specific order
+		$search_path = [
+			$project_path . '/resources/interfaces/*.php',
+			$project_path . '/resources/traits/*.php',
+			$project_path . '/resources/classes/*.php',
+			$project_path . '/*/*/resources/interfaces/*.php',
+			$project_path . '/*/*/resources/traits/*.php',
+			$project_path . '/*/*/resources/classes/*.php',
+			$project_path . '/core/authentication/resources/classes/plugins/*.php',
+		];
+
+		//get all php files for each path
+		$files = [];
+		foreach ($search_path as $path) {
+			$files = array_merge($files, glob($path));
 		}
 
-		//get all php files
-		$files = glob($project_root . '/*/*/resources/classes/*.php');
+		//reset the current array
+		$class_list = [];
 
-		//iterate over the files
+		//interfaces are ignored
+		$interface_list = [];
+
+		//store the class name (key) and the path (value)
 		foreach ($files as $file) {
-			include_once $file;
-			$class = basename($file, '.php');
+			$file_content = file_get_contents($file);
 
-			//check for the existence of the method
-			if (method_exists($class, $method_name)) {
-				$found_classes[$class] = $file;
+			// Remove block comments
+			$file_content = preg_replace('/\/\*.*?\*\//s', '', $file_content);
+			// Remove single-line comments
+			$file_content = preg_replace('/(\/\/|#).*$/m', '', $file_content);
+
+			// Detect the namespace
+			$namespace = '';
+			if (preg_match('/\bnamespace\s+([^;{]+)[;{]/', $file_content, $namespace_match)) {
+				$namespace = trim($namespace_match[1]) . '\\';
+			}
+
+			// Regex to capture class, interface, or trait declarations
+			// It optionally captures an "implements" clause
+			// Note: This regex is a simplified version and may need adjustments for edge cases
+			$pattern = '/\b(class|interface|trait)\s+(\w+)(?:\s+extends\s+\w+)?(?:\s+implements\s+([^\\{]+))?/';
+
+			if (preg_match_all($pattern, $file_content, $matches, PREG_SET_ORDER)) {
+				foreach ($matches as $match) {
+
+					// "class", "interface", or "trait"
+					$type = $match[1];
+
+					// The class/interface/trait name
+					$name = trim($match[2], " \n\r\t\v\x00\\");
+
+					// Combine the namespace and name
+					$full_name = $namespace . $name;
+
+					// Store the class/interface/trait with its file overwriting any existing declaration.
+					$class_list[$full_name] = $file;
+
+					// If it's a class that implements interfaces, process the implements clause.
+					if ($type === 'class' && isset($match[3]) && trim($match[3]) !== '') {
+						// Split the interface list by commas.
+						$interface_list = explode(',', $match[3]);
+						foreach ($interface_list as $interface) {
+							$interface_name = trim($interface, " \n\r\t\v\x00\\");
+							// Check that it is declared as an array so we can record the classes
+							if (empty($interface_list[$interface_name]))
+								$interface_list[$interface_name] = [];
+							// Record the classes that implement interface sorting by namspace and class name
+							$interface_list[$interface_name][] = $full_name;
+						}
+					}
+				}
+			} else {
+
+				//
+				// When the file is in the classes|interfaces|traits folder then
+				// we must assume it is a valid class as IonCube will encode the
+				// class name. So, we use the file name as the class name in the
+				// global  namespace and  set it,  checking first  to ensure the
+				// basename does not  override an already declared class file in
+				// order to mimic previous behaviour.
+				//
+
+				// use the basename as the class name
+				$class_name = basename($file, '.php');
+				if (!isset($class_list[$class_name])) {
+					$class_list[$class_name] = $file;
+				}
 			}
 		}
-		return $found_classes;
+		return $class_list;
 	}
 
 	private static function load_app_config_list() {