PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.6.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.6.0
2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 1.26.0 1.25.0 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.10.0 1.11.0 All 47 releases
thinkrank / includes / class-autoloader.php

class-autoloader.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 2.6.0, at includes/class-autoloader.php

198 lines 6.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Autoloader Class
4 *
5 * PSR-4 compliant autoloader for ThinkRank plugin
6 *
7 * @package ThinkRank\Core
8 * @since 1.0.0
9 */
10
11 declare(strict_types=1);
12
13 namespace ThinkRank\Core;
14
15 // Prevent direct access
16 if (!defined('ABSPATH')) {
17 exit;
18 }
19
20 /**
21 * Autoloader Class
22 *
23 * Handles automatic loading of plugin classes following PSR-4 standard
24 * Single Responsibility: Only handles class loading
25 *
26 * @since 1.0.0
27 */
28 class Autoloader {
29
30 /**
31 * Namespace prefix
32 *
33 * @var string
34 */
35 private const NAMESPACE_PREFIX = 'ThinkRank\\';
36
37 /**
38 * Base directory for the namespace prefix
39 *
40 * @var string
41 */
42 private string $base_dir;
43
44 /**
45 * Class map for faster loading
46 *
47 * @var array
48 */
49 private static array $class_map = [];
50
51 /**
52 * Constructor
53 */
54 public function __construct() {
55 $this->base_dir = THINKRANK_PLUGIN_DIR . 'includes/';
56 }
57
58 /**
59 * Register autoloader
60 *
61 * @return void
62 */
63 public static function register(): void {
64 $autoloader = new self();
65 spl_autoload_register([$autoloader, 'load_class']);
66 }
67
68 /**
69 * Load class file
70 *
71 * @param string $class_name Fully qualified class name
72 * @return bool True if file was loaded, false otherwise
73 */
74 public function load_class(string $class_name): bool {
75 // Check if class uses our namespace
76 if (strpos($class_name, self::NAMESPACE_PREFIX) !== 0) {
77 return false;
78 }
79
80 // Check class map first for performance
81 if (isset(self::$class_map[$class_name])) {
82 $file = self::$class_map[$class_name];
83 if (file_exists($file)) {
84 require_once $file;
85 return true;
86 }
87 }
88
89 // Get relative class name
90 $relative_class = substr($class_name, strlen(self::NAMESPACE_PREFIX));
91
92 // Convert namespace to file path
93 $file_path = $this->get_file_path($relative_class);
94
95 // Load file if it exists
96 if (file_exists($file_path)) {
97 require_once $file_path;
98
99 // Cache successful loads
100 self::$class_map[$class_name] = $file_path;
101
102 return true;
103 }
104
105 return false;
106 }
107
108 /**
109 * Convert class name to file path
110 *
111 * @param string $relative_class Relative class name
112 * @return string File path
113 */
114 private function get_file_path(string $relative_class): string {
115 // Replace namespace separators with directory separators
116 $file_path = str_replace('\\', DIRECTORY_SEPARATOR, $relative_class);
117
118 // Convert to lowercase and add class- prefix for WordPress convention
119 $parts = explode(DIRECTORY_SEPARATOR, $file_path);
120 $class_name = array_pop($parts);
121
122 // Handle special cases where the generic CamelCase→kebab conversion
123 // splits a brand/compound word (e.g. PageSpeed → page-speed) or an
124 // acronym differently than the actual file name on disk.
125 $special_cases = [
126 'OpenAI_Client' => 'openai-client',
127 'Claude_Client' => 'claude-client',
128 'Gemini_Client' => 'gemini-client',
129 'OpenRouter_Client' => 'openrouter-client',
130 'Google_PageSpeed_Client' => 'google-pagespeed-client',
131 'Analytics_Endpoint' => 'analytics-endpoint',
132 'SEO_Manager' => 'seo-manager',
133 'SEO_Plugin_Detector' => 'seo-plugin-detector',
134 'SEO_Notice' => 'seo-notice',
135 'LLMs_Txt_Endpoint' => 'llms-txt-endpoint',
136 'LLMs_Txt_Manager' => 'llms-txt-manager',
137 'Prompt_Builder' => 'prompt-builder',
138 'SEO_Prompts' => 'seo-prompts',
139 'Site_Identity_Prompts' => 'site-identity-prompts',
140 'Homepage_Prompts' => 'homepage-prompts',
141 'LLMs_Txt_Prompts' => 'llms-txt-prompts',
142 'Content_Brief_Prompts' => 'content-brief-prompts',
143 'SEOPress_Exporter' => 'seopress-exporter',
144 'AIOSEO_Exporter' => 'aioseo-exporter',
145 'Mcp_OAuth' => 'mcp-oauth',
146 'Google_OAuth_Proxy' => 'google-oauth-proxy',
147 ];
148
149 if (isset($special_cases[$class_name])) {
150 $class_file = 'class-' . $special_cases[$class_name] . '.php';
151 } else {
152 // Convert CamelCase to kebab-case for proper WordPress naming
153 // Handle consecutive uppercase letters (like SEO) properly
154 $class_name = preg_replace('/([A-Z]+)([A-Z][a-z])/', '$1-$2', $class_name);
155 $class_name = preg_replace('/([a-z])([A-Z])/', '$1-$2', $class_name);
156 $class_file = 'class-' . strtolower(str_replace('_', '-', $class_name)) . '.php';
157 }
158
159 // Build full path. Namespace segments with underscores map to
160 // hyphenated directories (Email_Report_Sections → email-report-sections).
161 $directory = !empty($parts) ? strtolower(str_replace('_', '-', implode(DIRECTORY_SEPARATOR, $parts))) . DIRECTORY_SEPARATOR : '';
162
163 $file = $this->base_dir . $directory . $class_file;
164
165 // Interfaces may use the WordPress interface- file prefix instead of class-
166 // (e.g. Email_Report_Section_Interface → interface-email-report-section.php).
167 if (!file_exists($file) && str_ends_with($class_file, '-interface.php')) {
168 $interface_file = $this->base_dir . $directory . 'interface-' . substr($class_file, strlen('class-'), -strlen('-interface.php')) . '.php';
169 if (file_exists($interface_file)) {
170 return $interface_file;
171 }
172 }
173
174 return $file;
175 }
176
177 /**
178 * Preload critical classes for performance
179 *
180 * @return void
181 */
182 public static function preload_critical_classes(): void {
183 $critical_classes = [
184 'ThinkRank\\Core\\Database',
185 'ThinkRank\\Core\\Settings',
186 'ThinkRank\\API\\Manager',
187 'ThinkRank\\Admin\\Manager',
188 ];
189
190 foreach ($critical_classes as $class) {
191 if (!class_exists($class)) {
192 $autoloader = new self();
193 $autoloader->load_class($class);
194 }
195 }
196 }
197 }
198