| 1 |
<?php |
| 2 |
/** |
| 3 |
* Plugin Class. |
| 4 |
* |
| 5 |
* @package zip-ai |
| 6 |
* @since 1.0.0 |
| 7 |
*/ |
| 8 |
|
| 9 |
namespace ZipAI\MCP; |
| 10 |
|
| 11 |
// Exit if accessed directly. |
| 12 |
if ( ! defined( 'ABSPATH' ) ) { |
| 13 |
exit; |
| 14 |
} |
| 15 |
|
| 16 |
use ZipAI\MCP\Classes\Core\Container; |
| 17 |
use ZipAI\MCP\Classes\Providers\Core_Service_Provider; |
| 18 |
use ZipAI\MCP\Classes\Providers\Abilities_Service_Provider; |
| 19 |
use ZipAI\MCP\Classes\Core\Helper; |
| 20 |
use ZipAI\MCP\Classes\Core\Service_Provider; |
| 21 |
|
| 22 |
if ( ! class_exists( '\ZipAI\MCP\Plugin' ) ) { |
| 23 |
/** |
| 24 |
* Plugin Class |
| 25 |
* |
| 26 |
* @since 1.0.0 |
| 27 |
*/ |
| 28 |
class Plugin { |
| 29 |
|
| 30 |
/** |
| 31 |
* Instance |
| 32 |
* |
| 33 |
* @access private |
| 34 |
* @var self|null Class Instance. |
| 35 |
* @since 1.0.0 |
| 36 |
*/ |
| 37 |
private static $instance; |
| 38 |
|
| 39 |
/** |
| 40 |
* The dependency injection container. |
| 41 |
* |
| 42 |
* @var Container |
| 43 |
*/ |
| 44 |
protected $container; |
| 45 |
|
| 46 |
/** |
| 47 |
* Service Providers. |
| 48 |
* |
| 49 |
* @var array<int, Service_Provider> |
| 50 |
*/ |
| 51 |
protected $providers = array(); |
| 52 |
|
| 53 |
/** |
| 54 |
* Initiator |
| 55 |
* |
| 56 |
* @since 1.0.0 |
| 57 |
* @return object initialized object of class. |
| 58 |
*/ |
| 59 |
public static function get_instance() { |
| 60 |
if ( ! isset( self::$instance ) ) { |
| 61 |
self::$instance = new self(); |
| 62 |
} |
| 63 |
return self::$instance; |
| 64 |
} |
| 65 |
|
| 66 |
/** |
| 67 |
* Constructor |
| 68 |
* |
| 69 |
* @since 1.0.0 |
| 70 |
*/ |
| 71 |
public function __construct() { |
| 72 |
// Must be first — everything below (Container included) autoloads |
| 73 |
// through it. |
| 74 |
spl_autoload_register( array( __CLASS__, 'autoload' ) ); |
| 75 |
|
| 76 |
$this->container = new Container(); |
| 77 |
|
| 78 |
$this->define_constants(); |
| 79 |
|
| 80 |
// Register Services. |
| 81 |
$this->register_services(); |
| 82 |
|
| 83 |
// Boot Services. |
| 84 |
add_action( 'plugins_loaded', array( $this, 'boot_services' ), 5 ); |
| 85 |
|
| 86 |
$this->setup_activation_hooks(); |
| 87 |
|
| 88 |
// Site scanner — event-driven memory enrichment (after auth). |
| 89 |
// \ZipAI\MCP\Classes\Core\Site_Scanner::register_hooks(); // TODO: class not yet committed. |
| 90 |
|
| 91 |
// Code snippet executor — runs AI-created snippets (PHP/JS/CSS/HTML). |
| 92 |
// This is the ONLY persistent hook needed — it auto-loads all snippets |
| 93 |
// created by any tool (cookie consent, tracking codes, custom CSS, etc.) |
| 94 |
\ZipAI\MCP\Classes\Core\Snippet_Executor::init(); |
| 95 |
|
| 96 |
// Plugin abilities toggler — listens for `activated_plugin` and |
| 97 |
// enables MCP abilities for mapped slugs regardless of activation |
| 98 |
// path (server-side ability, browser-proxied REST, admin UI, |
| 99 |
// WP-CLI). Idempotent + slug-map gated. |
| 100 |
\ZipAI\MCP\Classes\Core\Plugin_Abilities_Toggler::init(); |
| 101 |
|
| 102 |
// Imported-chrome reader — renders imported header/footer template |
| 103 |
// parts verbatim on ANY classic theme via a `template_include` |
| 104 |
// canvas takeover when `zipai_chrome_mode === 'takeover'` (written |
| 105 |
// by the importer) or a page carries the standalone marker meta. |
| 106 |
// Inert on block themes (FSE renders the parts natively) and on |
| 107 |
// sites that never imported. Registering the option doubles as the |
| 108 |
// backend's capability probe. |
| 109 |
\ZipAI\MCP\Classes\Core\Imported_Chrome::init(); |
| 110 |
|
| 111 |
// Privacy policy disclosure (WordPress Guideline 7). |
| 112 |
add_action( 'admin_init', array( $this, 'add_privacy_policy_content' ) ); |
| 113 |
} |
| 114 |
|
| 115 |
/** |
| 116 |
* Plugin autoloader — single runtime autoloader for everything the |
| 117 |
* plugin ships: its own classes (inc/) and the bundled |
| 118 |
* composer-managed libraries in lib/ (mcp-adapter, php-mcp-schema). |
| 119 |
* Composer's vendor/autoload.php is dev-tooling only and is never |
| 120 |
* loaded at runtime. |
| 121 |
* |
| 122 |
* @param string $class Fully qualified class name to load. |
| 123 |
* @return void |
| 124 |
*/ |
| 125 |
public static function autoload( string $class ): void { |
| 126 |
// Own classes — WordPress-style file names: namespace path |
| 127 |
// lowercased, CamelCase split with hyphens, underscores as hyphens. |
| 128 |
// ZipAI\MCP\Classes\Abilities\Core\RunWpCli -> inc/abilities/core/run-wp-cli.php. |
| 129 |
$own_prefix = 'ZipAI\MCP\Classes\\'; |
| 130 |
if ( 0 === strpos( $class, $own_prefix ) ) { |
| 131 |
$filename = preg_replace( |
| 132 |
array( '/([a-z])([A-Z])/', '/_/', '/\\\\/' ), |
| 133 |
array( '$1-$2', '-', '/' ), |
| 134 |
substr( $class, strlen( $own_prefix ) ) |
| 135 |
); |
| 136 |
|
| 137 |
if ( is_string( $filename ) ) { |
| 138 |
$file = __DIR__ . '/inc/' . strtolower( $filename ) . '.php'; |
| 139 |
|
| 140 |
if ( is_readable( $file ) ) { |
| 141 |
require_once $file; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable -- Path is derived from the class name. |
| 142 |
} |
| 143 |
} |
| 144 |
return; |
| 145 |
} |
| 146 |
|
| 147 |
// Bundled lib/ packages — strict PSR-4, directory case preserved. |
| 148 |
$psr4 = array( |
| 149 |
'WP\MCP\\' => 'lib/mcp-adapter/includes/', |
| 150 |
'WP\McpSchema\\' => 'lib/php-mcp-schema/src/', |
| 151 |
); |
| 152 |
|
| 153 |
foreach ( $psr4 as $prefix => $base_dir ) { |
| 154 |
if ( 0 === strpos( $class, $prefix ) ) { |
| 155 |
$file = __DIR__ . '/' . $base_dir . str_replace( '\\', '/', substr( $class, strlen( $prefix ) ) ) . '.php'; |
| 156 |
|
| 157 |
if ( is_readable( $file ) ) { |
| 158 |
require_once $file; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable -- Path is derived from a fixed prefix map. |
| 159 |
} |
| 160 |
return; |
| 161 |
} |
| 162 |
} |
| 163 |
} |
| 164 |
|
| 165 |
/** |
| 166 |
* Register Service Providers. |
| 167 |
* |
| 168 |
* @return void |
| 169 |
*/ |
| 170 |
private function register_services() { |
| 171 |
$this->providers[] = new Core_Service_Provider( $this->container ); |
| 172 |
$this->providers[] = new Abilities_Service_Provider( $this->container ); |
| 173 |
|
| 174 |
foreach ( $this->providers as $provider ) { |
| 175 |
$provider->register(); |
| 176 |
} |
| 177 |
} |
| 178 |
|
| 179 |
/** |
| 180 |
* Boot Service Providers. |
| 181 |
* |
| 182 |
* @return void |
| 183 |
*/ |
| 184 |
public function boot_services() { |
| 185 |
$this->load_vendor_dependencies(); |
| 186 |
|
| 187 |
foreach ( $this->providers as $provider ) { |
| 188 |
$provider->boot(); |
| 189 |
} |
| 190 |
} |
| 191 |
|
| 192 |
/** |
| 193 |
* Define the required constants. |
| 194 |
* |
| 195 |
* @since 1.0.0 |
| 196 |
* @return void |
| 197 |
*/ |
| 198 |
public function define_constants() { |
| 199 |
define( 'ZIPAI_MCP_FILE', __DIR__ . '/zip-ai.php' ); |
| 200 |
define( 'ZIPAI_MCP_DIR', plugin_dir_path( ZIPAI_MCP_FILE ) ); |
| 201 |
define( 'ZIPAI_MCP_URL', plugins_url( '/', ZIPAI_MCP_FILE ) ); |
| 202 |
define( 'ZIPAI_MCP_VERSION', '0.0.10' ); |
| 203 |
define( 'ZIPAI_MCP_MENU_SLUG', 'zip-ai' ); |
| 204 |
|
| 205 |
// Base URL for ZIP AI credit server. |
| 206 |
if ( ! defined( 'ZIPAI_MCP_BASE_URL' ) ) { |
| 207 |
define( 'ZIPAI_MCP_BASE_URL', 'https://credits.zipwp.com' ); |
| 208 |
} |
| 209 |
|
| 210 |
if ( ! defined( 'ZIPAI_MCP_MIDDLEWARE' ) ) { |
| 211 |
define( 'ZIPAI_MCP_MIDDLEWARE', 'https://app.zipwp.com/auth/' ); |
| 212 |
} |
| 213 |
|
| 214 |
if ( ! defined( 'ZIPAI_API_BASE' ) ) { |
| 215 |
define( 'ZIPAI_API_BASE', 'https://api.zipwp.com/api/' ); |
| 216 |
} |
| 217 |
|
| 218 |
// Base URL for direct server calls (e.g. inline-edit). Override via |
| 219 |
// wp-config.php or the `zipai_brain_url` filter. |
| 220 |
if ( ! defined( 'ZIPAI_BRAIN_URL' ) ) { |
| 221 |
define( 'ZIPAI_BRAIN_URL', 'https://brain.zipwp.com' ); |
| 222 |
} |
| 223 |
|
| 224 |
// API endpoint for credit server. |
| 225 |
if ( ! defined( 'ZIPAI_MCP_CREDIT_SERVER_API' ) ) { |
| 226 |
define( 'ZIPAI_MCP_CREDIT_SERVER_API', ZIPAI_MCP_BASE_URL . '/api/' ); |
| 227 |
} |
| 228 |
} |
| 229 |
|
| 230 |
/** |
| 231 |
* Load bundled dependencies (mcp-adapter). |
| 232 |
* |
| 233 |
* @since 1.0.0 |
| 234 |
* @return void |
| 235 |
*/ |
| 236 |
private function load_vendor_dependencies() { |
| 237 |
// Initialize mcp-adapter plugin if not already loaded. It is vendored |
| 238 |
// in lib/mcp-adapter (git-committed) and its classes are served by the |
| 239 |
// plugin autoloader (Plugin::autoload). The library self-bootstraps |
| 240 |
// (defines its own constants, runs its autoloader and calls |
| 241 |
// \WP\MCP\Plugin::instance()) on require, and its self-autoload expects |
| 242 |
// a nested vendor/ it doesn't have — so we tell it to skip it, else it |
| 243 |
// prints a false "Composer autoloader was not found" admin notice and |
| 244 |
// bails before Plugin::instance(). |
| 245 |
if ( ! defined( 'WP_MCP_AUTOLOAD' ) ) { |
| 246 |
define( 'WP_MCP_AUTOLOAD', false ); |
| 247 |
} |
| 248 |
if ( ! defined( 'WP_MCP_VERSION' ) && file_exists( ZIPAI_MCP_DIR . 'lib/mcp-adapter/mcp-adapter.php' ) ) { |
| 249 |
require_once ZIPAI_MCP_DIR . 'lib/mcp-adapter/mcp-adapter.php'; |
| 250 |
} |
| 251 |
} |
| 252 |
|
| 253 |
/** |
| 254 |
* Setup plugin activation and deactivation hooks. |
| 255 |
* |
| 256 |
* @since 1.0.0 |
| 257 |
* @return void |
| 258 |
*/ |
| 259 |
public function setup_activation_hooks() { |
| 260 |
register_activation_hook( ZIPAI_MCP_FILE, array( $this, 'plugin_activated' ) ); |
| 261 |
register_deactivation_hook( ZIPAI_MCP_FILE, array( $this, 'plugin_deactivated' ) ); |
| 262 |
} |
| 263 |
|
| 264 |
/** |
| 265 |
* Plugin activation callback. |
| 266 |
* |
| 267 |
* @since 1.0.0 |
| 268 |
* @return void |
| 269 |
*/ |
| 270 |
public function plugin_activated() { |
| 271 |
// Add the required capability to administrator role. |
| 272 |
$this->add_plugin_capabilities(); |
| 273 |
|
| 274 |
// Generate and register shared secret with the server on activation. |
| 275 |
$this->register_hmac_secret(); |
| 276 |
} |
| 277 |
|
| 278 |
/** |
| 279 |
* Plugin deactivation callback. |
| 280 |
* |
| 281 |
* @since 1.0.0 |
| 282 |
* @return void |
| 283 |
*/ |
| 284 |
public function plugin_deactivated() { |
| 285 |
// Remove the plugin capabilities from all roles. |
| 286 |
$this->remove_plugin_capabilities(); |
| 287 |
|
| 288 |
// Clear scheduled site scan. |
| 289 |
\ZipAI\MCP\Classes\Core\Site_Scanner::unschedule(); |
| 290 |
} |
| 291 |
|
| 292 |
/** |
| 293 |
* Suggest privacy policy content per WordPress Guideline 7. |
| 294 |
* |
| 295 |
* Discloses what data is sent to the ZIP AI cloud service. |
| 296 |
* |
| 297 |
* @since 1.0.0 |
| 298 |
* @return void |
| 299 |
*/ |
| 300 |
public function add_privacy_policy_content() { |
| 301 |
$policy_text = '<h2>ZIP AI Assistant</h2> |
| 302 |
<p>This site uses the ZIP AI Assistant plugin ("ZIP AI"), which connects to a cloud-based AI service operated by Starter Templates (Starter Templates, a Brainstorm Force product).</p> |
| 303 |
|
| 304 |
<h3>What data is collected</h3> |
| 305 |
<p>When authenticated and actively using ZIP AI, the following data may be sent to our servers:</p> |
| 306 |
<ul> |
| 307 |
<li><strong>Chat messages</strong> — Messages you send to the AI assistant and the assistant\'s responses.</li> |
| 308 |
<li><strong>Site structure data</strong> — Page titles, post counts, active plugin names, active theme name, and content categories. This helps ZIP AI understand your site and provide relevant suggestions.</li> |
| 309 |
<li><strong>Site identity</strong> — Site title, tagline, language, and domain name.</li> |
| 310 |
<li><strong>E-commerce data</strong> (if applicable) — Product counts, product categories, currency, and whether reviews are enabled. No individual product details, prices, or customer data is sent.</li> |
| 311 |
</ul> |
| 312 |
|
| 313 |
<h3>What data is NOT collected</h3> |
| 314 |
<ul> |
| 315 |
<li>Page or post content/body text</li> |
| 316 |
<li>Customer or visitor personal information</li> |
| 317 |
<li>Passwords, payment details, or financial data</li> |
| 318 |
<li>Email addresses of site visitors</li> |
| 319 |
<li>Analytics or traffic data</li> |
| 320 |
</ul> |
| 321 |
|
| 322 |
<h3>How data is used</h3> |
| 323 |
<p>Data is used solely to power the AI assistant\'s responses and to build a memory of your site preferences so you don\'t have to repeat yourself across conversations. You can clear all stored memory at any time via the "Clear Site Memory" option in the ZIP AI menu.</p> |
| 324 |
|
| 325 |
<h3>Data retention</h3> |
| 326 |
<p>Chat messages and memory data are stored on our servers for as long as your account is active. You can request deletion at any time by clearing your site memory or contacting support.</p> |
| 327 |
|
| 328 |
<h3>Third-party services</h3> |
| 329 |
<p>ZIP AI uses AI language models (such as Google Gemini and Anthropic Claude) to process your messages. Your messages may be sent to these providers for processing. Please refer to their respective privacy policies:</p> |
| 330 |
<ul> |
| 331 |
<li><a href="https://policies.google.com/privacy">Google Privacy Policy</a></li> |
| 332 |
<li><a href="https://www.anthropic.com/privacy">Anthropic Privacy Policy</a></li> |
| 333 |
</ul> |
| 334 |
|
| 335 |
<p>For more information, please see our <a href="https://developer.brainstormforce.com/privacy-policy/">Privacy Policy</a>.</p>'; |
| 336 |
|
| 337 |
wp_add_privacy_policy_content( 'ZIP AI Assistant', $policy_text ); |
| 338 |
} |
| 339 |
|
| 340 |
/** |
| 341 |
* Add required capabilities to administrator role. |
| 342 |
* |
| 343 |
* @since 1.0.0 |
| 344 |
* @return void |
| 345 |
*/ |
| 346 |
private function add_plugin_capabilities() { |
| 347 |
// Get the administrator role. |
| 348 |
$admin_role = get_role( 'administrator' ); |
| 349 |
|
| 350 |
if ( $admin_role ) { |
| 351 |
// Add the required capability for ZipWP MCP management. |
| 352 |
$admin_role->add_cap( 'manage_zip_mcp_assistant', true ); |
| 353 |
} |
| 354 |
} |
| 355 |
|
| 356 |
/** |
| 357 |
* Remove plugin capabilities from all roles. |
| 358 |
* |
| 359 |
* @since 1.0.0 |
| 360 |
* @return void |
| 361 |
*/ |
| 362 |
private function remove_plugin_capabilities() { |
| 363 |
// Get all roles. |
| 364 |
$roles = wp_roles(); |
| 365 |
|
| 366 |
// Remove the capability from all roles that have it. |
| 367 |
foreach ( $roles->role_objects as $role ) { |
| 368 |
if ( $role->has_cap( 'manage_zip_mcp_assistant' ) ) { |
| 369 |
$role->remove_cap( 'manage_zip_mcp_assistant' ); |
| 370 |
} |
| 371 |
} |
| 372 |
} |
| 373 |
|
| 374 |
/** |
| 375 |
* Register HMAC shared secret with the server. |
| 376 |
* |
| 377 |
* @since 1.0.0 |
| 378 |
* @return void |
| 379 |
*/ |
| 380 |
private function register_hmac_secret() { |
| 381 |
// Only register if not already registered. |
| 382 |
if ( ! Helper::is_hmac_registered() ) { |
| 383 |
$registration_result = Helper::register_shared_secret_with_laravel(); |
| 384 |
|
| 385 |
if ( isset( $registration_result['error'] ) ) { |
| 386 |
// Note: Error logging removed for production. |
| 387 |
unset( $registration_result ); |
| 388 |
} |
| 389 |
} |
| 390 |
} |
| 391 |
} |
| 392 |
|
| 393 |
/** |
| 394 |
* Kicking this off by calling 'get_instance()' method |
| 395 |
*/ |
| 396 |
Plugin::get_instance(); |
| 397 |
} |
| 398 |
|