PluginProbe
wpDataTables – WordPress Data Table, Dynamic Tables & Table Charts Plugin / trunk
wpDataTables – WordPress Data Table, Dynamic Tables & Table Charts Plugin vtrunk
6.5.1.7 6.5.1.6 6.5.1.5 6.5.1.4 6.5.1.3 6.5.1.2 6.5.1.1 6.5.0.9 6.5.0.8 6.5.0.7 6.5.0.6 trunk 3.4.2.40 3.4.2.41 3.4.2.42 3.4.2.43 3.4.2.44 3.4.2.45 3.4.2.46 3.4.2.47 3.4.2.48 3.4.2.49 3.4.2.50 6.3.2 6.3.3.1 All 47 releases
wpdatatables / Infrastructure / WP / MCP / WdtmcpAdapterCompatibility.php

WdtmcpAdapterCompatibility.php in wpDataTables – WordPress Data Table, Dynamic Tables & Table Charts Plugin trunk, at Infrastructure/WP/MCP/WdtmcpAdapterCompatibility.php

133 lines 4.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Detects whether the loaded MCP Adapter copy can back the wpDataTables integration.
5 *
6 * @package wpDataTables
7 */
8
9 namespace WDTMCP\Infrastructure\WP\MCP;
10
11 use ReflectionClass;
12 use ReflectionMethod;
13 use Throwable;
14
15 defined('ABSPATH') or die('Access denied.');
16
17 /**
18 * Reflection-only checks against WP\MCP classes. Safe to load before any wpDataTables
19 * subclass of those classes is required.
20 */
21 class WdtmcpAdapterCompatibility
22 {
23 /**
24 * Directory the given MCP Adapter class was loaded from.
25 *
26 * The PSR-4 prefix is stripped from the file path so copies with a different folder layout
27 * still resolve to a comparable root.
28 *
29 * @param string $className Fully qualified class name inside the WP\MCP namespace.
30 * @return string Root directory, or an empty string when it cannot be determined.
31 */
32 public static function classRoot(string $className): string
33 {
34 try {
35 $file = (new ReflectionClass($className))->getFileName();
36 } catch (Throwable $exception) {
37 return '';
38 }
39
40 if (! is_string($file) || '' === $file) {
41 return '';
42 }
43
44 $relative = str_replace('\\', '/', substr($className, strlen('WP\\MCP\\'))) . '.php';
45 $file = str_replace('\\', '/', $file);
46 $position = strrpos($file, $relative);
47
48 return false === $position ? '' : substr($file, 0, $position);
49 }
50
51 /**
52 * Collect the reasons the loaded MCP Adapter cannot run the wpDataTables integration.
53 *
54 * Any plugin may load its own MCP Adapter build before wpDataTables does, so the copy that
55 * ends up running can be a different version, a namespace-prefixed build, or a mix of two
56 * copies resolved by competing autoloaders. Extending such a copy is a fatal error, so the
57 * integration stays off whenever the classes it builds on are missing, final, or still
58 * abstract.
59 *
60 * @param array<string, list<string>> $extendedClasses Map of parent FQCN => methods our subclass implements.
61 * @return list<string> Human-readable problems; empty when the loaded copy is usable.
62 */
63 public static function findProblems(array $extendedClasses): array
64 {
65 $requiredClasses = array_merge(
66 array(
67 'WP\\MCP\\Core\\McpAdapter',
68 'WP\\MCP\\Transport\\Infrastructure\\McpTransportContext',
69 ),
70 array_keys($extendedClasses)
71 );
72
73 $problems = array();
74 $roots = array();
75
76 foreach ($requiredClasses as $className) {
77 if (! class_exists($className)) {
78 /* translators: %s: fully qualified class name */
79 $problems[] = sprintf(__('missing class %s', 'wpdatatables'), $className);
80 continue;
81 }
82
83 $root = self::classRoot($className);
84
85 if ('' !== $root) {
86 $roots[ $root ] = true;
87 }
88
89 if (! isset($extendedClasses[ $className ])) {
90 continue;
91 }
92
93 $reflection = new ReflectionClass($className);
94
95 if ($reflection->isFinal()) {
96 /* translators: %s: fully qualified class name */
97 $problems[] = sprintf(__('class %s cannot be extended', 'wpdatatables'), $className);
98 continue;
99 }
100
101 foreach ($reflection->getMethods(ReflectionMethod::IS_ABSTRACT) as $method) {
102 if (! in_array($method->getName(), $extendedClasses[ $className ], true)) {
103 $problems[] = sprintf(
104 /* translators: 1: fully qualified class name, 2: method name */
105 __('class %1$s expects an unsupported %2$s() method', 'wpdatatables'),
106 $className,
107 $method->getName()
108 );
109 }
110 }
111 }
112
113 if (
114 class_exists('WP\\MCP\\Core\\McpAdapter')
115 && ! method_exists('WP\\MCP\\Core\\McpAdapter', 'create_server')
116 ) {
117 $problems[] = __('missing method McpAdapter::create_server()', 'wpdatatables');
118 }
119
120 // Several plugins bundling the same adapter version is normal and works, so the copies in
121 // use are reported only as context for a problem that was already found.
122 if ($problems && count($roots) > 1) {
123 $problems[] = sprintf(
124 /* translators: %s: comma-separated list of directories */
125 __('classes come from more than one MCP Adapter copy (%s)', 'wpdatatables'),
126 implode(', ', array_keys($roots))
127 );
128 }
129
130 return $problems;
131 }
132 }
133