true, 'upload_files' => true, 'edit_posts' => true, 'edit_published_posts' => true, 'edit_pages' => true, 'edit_published_pages' => true, 'publish_posts' => true, 'publish_pages' => true, ]; $role = get_role(self::ROLE); if (!$role) { add_role(self::ROLE, self::DISPLAY_NAME, $caps); return; } self::sync_role_label(); if (get_option(self::OPTION_SCOPE, null) === null) { $legacy_scope = sanitize_key(get_option(self::LEGACY_OPTION_AUTONOMY, 'draft_only')); if (!array_key_exists($legacy_scope, self::permission_levels())) { $legacy_scope = 'draft_only'; } update_option(self::OPTION_SCOPE, $legacy_scope, false); } foreach ($caps as $cap => $grant) { if ($grant && !$role->has_cap($cap)) { $role->add_cap($cap, true); } } } public static function display_name() { return self::DISPLAY_NAME; } protected static function sync_role_label() { $roles = wp_roles(); if (!$roles || empty($roles->roles[self::ROLE])) { return; } if (($roles->roles[self::ROLE]['name'] ?? '') === self::DISPLAY_NAME) { return; } $roles->roles[self::ROLE]['name'] = self::DISPLAY_NAME; $roles->role_names[self::ROLE] = self::DISPLAY_NAME; update_option($roles->role_key, $roles->roles); } public static function maybe_migrate_operator_profile() { if (!is_admin() || !current_user_can('manage_options')) { return; } self::sync_role_label(); $user_id = self::get_user_id(); if (!$user_id) { $legacy = get_user_by('login', self::username()); if ($legacy) { $user_id = intval($legacy->ID); update_option(self::OPTION_USER_ID, $user_id, false); } } if ($user_id) { self::sync_user_profile($user_id); } } protected static function sync_user_profile($user_id) { $user_id = intval($user_id); if ($user_id <= 0) { return; } $user = get_userdata($user_id); if (!$user) { return; } if (!in_array(self::ROLE, (array) $user->roles, true)) { $user->set_role(self::ROLE); } $needs_update = false; $data = ['ID' => $user_id]; if ($user->display_name !== self::DISPLAY_NAME) { $data['display_name'] = self::DISPLAY_NAME; $needs_update = true; } if ($user->nickname !== self::DISPLAY_NAME) { $data['nickname'] = self::DISPLAY_NAME; $needs_update = true; } $new_email = self::available_email($user_id); if ($new_email && (strpos((string) $user->user_email, '.invalid') !== false || strpos((string) $user->user_email, '@') === false)) { $email_owner = email_exists($new_email); if (!$email_owner || intval($email_owner) === $user_id) { $data['user_email'] = $new_email; $needs_update = true; } } if ($needs_update) { wp_update_user($data); } update_user_meta($user_id, 'nickname', self::DISPLAY_NAME); update_user_meta( $user_id, 'description', __('Transparent execution identity used for administrator-started content creation and improvement.', 'ai-webmaster-studio') ); update_user_meta($user_id, 'show_admin_bar_front', 'false'); update_user_meta($user_id, 'rich_editing', 'false'); update_user_meta($user_id, 'aiwest_technical_operator', 'yes'); } public static function permission_levels() { return [ 'draft_only' => [ 'label' => __('Draft only', 'ai-webmaster-studio'), 'description' => __('AI may create content as drafts. Publishing requires an administrator decision.', 'ai-webmaster-studio'), ], 'content_update' => [ 'label' => __('Create and update content', 'ai-webmaster-studio'), 'description' => __('AI may create drafts and update existing content according to AI Policies.', 'ai-webmaster-studio'), ], 'publish_allowed' => [ 'label' => __('Publish allowed', 'ai-webmaster-studio'), 'description' => __('AI may publish content only when AI Policies and module settings allow it.', 'ai-webmaster-studio'), ], ]; } public static function is_enabled() { return get_option(self::OPTION_ENABLED, 'no') === 'yes' && get_option(self::OPTION_CONSENT, 'no') === 'yes' && self::get_user_id() > 0; } public static function scope() { $scope = sanitize_key(get_option(self::OPTION_SCOPE, 'draft_only')); return array_key_exists($scope, self::permission_levels()) ? $scope : 'draft_only'; } public static function can_update_content() { return self::is_enabled() && in_array(self::scope(), ['content_update', 'publish_allowed'], true); } public static function can_publish() { return self::is_enabled() && self::scope() === 'publish_allowed'; } public static function default_post_status($requested = 'draft') { $requested = sanitize_key($requested); if (!in_array($requested, ['draft', 'pending', 'publish', 'private'], true)) { $requested = 'draft'; } if ($requested === 'publish' && !self::can_publish()) { return 'draft'; } return $requested; } public static function post_author_id($fallback = 0) { if (self::is_enabled()) { return self::get_user_id(); } $fallback = intval($fallback); if ($fallback > 0) return $fallback; $current = get_current_user_id(); return $current ? intval($current) : 1; } public static function get_user_id() { $user_id = intval(get_option(self::OPTION_USER_ID, 0)); if ($user_id > 0 && get_userdata($user_id)) { return $user_id; } return 0; } public static function username() { return 'ai-webmaster-operator'; } protected static function email_domain() { $host = (string) wp_parse_url(home_url(), PHP_URL_HOST); $host = strtolower(preg_replace('/[^a-z0-9.-]/', '', $host)); $host = preg_replace('/^www\./', '', $host); if (!$host || strpos($host, '.') === false) { $admin_email = sanitize_email((string) get_option('admin_email', '')); if ($admin_email && strpos($admin_email, '@') !== false) { $parts = explode('@', $admin_email); $host = strtolower(preg_replace('/[^a-z0-9.-]/', '', end($parts))); } } if (!$host || strpos($host, '.') === false) { $host = 'example.com'; } return $host; } public static function email() { return sanitize_email('ai-webmaster-operator@' . self::email_domain()); } public static function available_email($user_id = 0) { $user_id = absint($user_id); $host = self::email_domain(); $candidates = ['ai-webmaster-operator@' . $host]; if ($user_id) { $candidates[] = 'ai-webmaster-operator-' . $user_id . '@' . $host; } for ($i = 2; $i <= 20; $i++) { $candidates[] = 'ai-webmaster-operator-' . $i . '@' . $host; } foreach ($candidates as $candidate) { $candidate = sanitize_email($candidate); $owner = $candidate ? email_exists($candidate) : false; if ($candidate && (!$owner || intval($owner) === $user_id)) { return $candidate; } } return sanitize_email('ai-webmaster-operator-' . wp_generate_password(6, false, false) . '@' . $host); } public static function ensure_user() { self::register_role(); $user_id = self::get_user_id(); if ($user_id) { $user = get_userdata($user_id); if ($user && !in_array(self::ROLE, (array) $user->roles, true)) { $user->set_role(self::ROLE); } self::sync_user_profile($user_id); return $user_id; } $username = self::username(); $user = get_user_by('login', $username); if ($user) { $user_id = intval($user->ID); $user->set_role(self::ROLE); update_option(self::OPTION_USER_ID, $user_id, false); self::sync_user_profile($user_id); return $user_id; } $user_id = wp_insert_user([ 'user_login' => $username, 'user_pass' => wp_generate_password(64, true, true), 'user_email' => self::available_email(), 'display_name' => self::DISPLAY_NAME, 'nickname' => self::DISPLAY_NAME, 'role' => self::ROLE, 'show_admin_bar_front' => 'false', 'rich_editing' => 'false', ]); if (is_wp_error($user_id)) { if (class_exists('AIWEST_Logger')) { AIWEST_Logger::error('AI Webmaster Studio Operator could not be created.', ['error' => $user_id->get_error_message()]); } return $user_id; } update_option(self::OPTION_USER_ID, intval($user_id), false); update_user_meta(intval($user_id), 'show_admin_bar_front', 'false'); update_user_meta(intval($user_id), 'rich_editing', 'false'); update_user_meta(intval($user_id), 'aiwest_technical_operator', 'yes'); self::sync_user_profile(intval($user_id)); if (class_exists('AIWEST_Logger')) { AIWEST_Logger::info('AI Webmaster Studio Operator created.', ['user_id' => intval($user_id)]); } return intval($user_id); } public static function enable($scope = 'draft_only') { $levels = self::permission_levels(); $scope = sanitize_key($scope); if (!array_key_exists($scope, $levels)) { $scope = 'draft_only'; } $user_id = self::ensure_user(); if (is_wp_error($user_id)) { return $user_id; } update_option(self::OPTION_ENABLED, 'yes', false); update_option(self::OPTION_CONSENT, 'yes', false); update_option(self::OPTION_SCOPE, $scope, false); if (class_exists('AIWEST_Logger')) { AIWEST_Logger::info('AI Webmaster Studio Operator enabled.', ['user_id' => intval($user_id), 'scope' => $scope]); } return intval($user_id); } public static function disable() { update_option(self::OPTION_ENABLED, 'no', false); if (class_exists('AIWEST_Logger')) { AIWEST_Logger::info('AI Webmaster Studio Operator disabled.'); } } public static function status_label() { if (!self::get_user_id()) { return __('Not created', 'ai-webmaster-studio'); } return self::is_enabled() ? __('Enabled', 'ai-webmaster-studio') : __('Disabled', 'ai-webmaster-studio'); } public static function scope_label() { $levels = self::permission_levels(); $level = $levels[self::scope()] ?? $levels['draft_only']; return (string) $level['label']; } public static function explain_decision($operation, $module = null, $context = []) { $policy = class_exists('AIWEST_Policy') ? AIWEST_Policy::evaluate($operation, $module, $context) : ['allowed' => true, 'needs_approval' => false, 'denied' => false, 'decision' => 'allow', 'reason' => 'AI Policies module is unavailable.']; $operator_enabled = self::is_enabled(); $can_publish = self::can_publish(); $requested_status = sanitize_key($context['post_status'] ?? 'draft'); $publish_blocked = $requested_status === 'publish' && !$can_publish; $allowed = $operator_enabled && !empty($policy['allowed']) && !$publish_blocked; $needs_approval = !$operator_enabled || !empty($policy['needs_approval']) || $publish_blocked; $denied = !empty($policy['denied']); return [ 'operation' => sanitize_key($operation), 'module' => sanitize_key($module ?: ($policy['module'] ?? 'content')), 'operator_enabled' => $operator_enabled, 'scope' => self::scope(), 'ai_policy_decision' => sanitize_key($policy['decision'] ?? 'allow'), 'ai_decision' => $denied ? 'rejected' : ($allowed ? 'approved' : 'requires_administrator_decision'), 'ai_confidence' => $allowed ? 0.86 : 0.64, 'allowed' => $allowed, 'needs_approval' => $needs_approval, 'denied' => $denied, 'reason' => $operator_enabled ? sanitize_text_field($policy['reason'] ?? 'Operation evaluated against AI Policies.') : __('AI Webmaster Studio Operator is not enabled, so the action requires an administrator decision.', 'ai-webmaster-studio'), 'recommendation' => $allowed ? __('The operation can be executed and recorded in the AI action history.', 'ai-webmaster-studio') : __('Keep the operation as a draft or administrator decision queue item.', 'ai-webmaster-studio'), ]; } public static function annotate_post($post_id, $operation, $decision = []) { $post_id = intval($post_id); if ($post_id <= 0) return; $decision = is_array($decision) ? $decision : []; update_post_meta($post_id, self::META_OPERATOR, 'yes'); update_post_meta($post_id, '_aiwest_operator_user_id', self::post_author_id()); update_post_meta($post_id, '_aiwest_operator_operation', sanitize_key($operation)); update_post_meta($post_id, '_aiwest_ai_decision', sanitize_key($decision['ai_decision'] ?? 'approved')); update_post_meta($post_id, '_aiwest_ai_confidence', sanitize_text_field((string)($decision['ai_confidence'] ?? ''))); update_post_meta($post_id, '_aiwest_ai_reason', sanitize_textarea_field($decision['reason'] ?? '')); update_post_meta($post_id, '_aiwest_ai_recommendation', sanitize_textarea_field($decision['recommendation'] ?? '')); } } AIWEST_Technical_Operator::init();