is_plugin_active() ) { return (bool) $refused; } return ! AcrossAI_Integration_Settings::is_enabled( $this->slug() ); } /** * Toolset key. * * @since 0.0.34 * @return string */ public function group(): string { return self::TAB_GROUP; } /** * Display name for the toolset. * * Drops the qualifier the opt-in label carries: that suffix names what the switch governs, which * matters beside the switch and reads as a different product in a list of toolsets. * * @since 0.0.34 * @return string */ public function toolset_label(): string { return __( 'WPForms', 'acrossai-abilities-manager' ); } /** * What this toolset covers, for an MCP client. * * States the write switch plainly. An assistant that meets `wpforms_writes_disabled` without * knowing a switch exists either gives up or retries the same call. * * @since 0.0.34 * @return string */ public function toolset_description(): string { return __( 'WPForms: list and read forms, inspect the form editing schema, read form statistics, and create forms, add and update fields, and change form settings. These abilities come from WPForms itself, not from this plugin. The four writing abilities depend on the WPForms write switch on the Integrations settings screen; with it off they are refused with wpforms_writes_disabled and the form is unchanged. Form entries, notifications and confirmations are not covered here. action=discover lists this group; action=info returns schemas; action=execute runs one ability.', 'acrossai-abilities-manager' ); } /** * Ability-name prefix WPForms registers under. * * BARE, with no trailing slash. `AcrossAI_Ability_Group_Tagger` takes the segment BEFORE the * first slash and looks that value up, so `'wpforms/'` would never match anything and all eight * abilities would stay in the catch-all while appearing to be claimed. That is not hypothetical: * it is issue #209, where `WPCode` declares `'wpcode/'` and its five abilities are in `other`. * * @since 0.0.34 * @return string[] */ public function ability_prefixes(): array { return array( 'wpforms' ); } /** * Whether WPForms is present. * * @since 0.0.34 * @return bool */ public function is_active(): bool { return $this->is_plugin_active(); } /** * Human-readable label for the card and tab. * * @since 0.0.34 * @return string */ protected function label(): string { return __( 'WPForms (form writing)', 'acrossai-abilities-manager' ); } /** * Whether WPForms is loaded on the current site. * * A compound check on two stable public symbols per SEC-002: a single `class_exists()` is * spoofable by any plugin that happens to define the same name. `wpforms_setting()` is the * second symbol because it is the function WPForms reads the write switch through, so this test * follows the same code path the toggle ultimately affects — the reasoning ACF uses for * `acf_get_setting()`. * * @since 0.0.34 * @return bool */ protected function is_plugin_active(): bool { return defined( 'WPFORMS_VERSION' ) && function_exists( 'wpforms_setting' ); } /** * Permit WPForms' write abilities. * * Called by the base class from `maybe_enable()` ONLY when the toggle is on AND * `is_plugin_active()` is true. Runs at `plugins_loaded` P20; WPForms reads the filter inside a * permission callback at request time, so attaching here is early enough on the same request. * * @since 0.0.34 * @return void */ protected function enable_filter(): void { add_filter( self::WRITE_FILTER, '__return_true' ); } /** * Fixed readonly list of the eight abilities WPForms registers. * * Copied from the registration calls in `src/Integrations/Abilities/Abilities.php` and the Lite * subclass. Display-only: these rows never reach `wp_get_abilities()`, so nothing at runtime * forces them to match, and ACF's equivalent list drifted to two abilities that do not exist. * `Test_Integration_Row_Accuracy` asserts every declared slug resolves whenever WPForms is * active, which is what keeps this honest. * * @since 0.0.34 * @return array */ protected function abilities(): array { return array( array( 'slug' => 'wpforms/list-forms', 'label' => __( 'List Forms', 'acrossai-abilities-manager' ), 'description' => __( 'List the WPForms forms on this site with their metadata, filtered by published, draft or trashed status.', 'acrossai-abilities-manager' ), ), array( 'slug' => 'wpforms/get-form', 'label' => __( 'Get Form', 'acrossai-abilities-manager' ), 'description' => __( 'Read one form: its fields, settings and notifications as WPForms stores them.', 'acrossai-abilities-manager' ), ), array( 'slug' => 'wpforms/describe-editing-schema', 'label' => __( 'Describe Editing Schema', 'acrossai-abilities-manager' ), 'description' => __( 'Describe the shape WPForms expects when a form is edited — the field types available and the properties each one accepts. Read this before adding or updating a field.', 'acrossai-abilities-manager' ), ), array( 'slug' => 'wpforms/get-form-stats', 'label' => __( 'Get Form Stats', 'acrossai-abilities-manager' ), 'description' => __( 'Read the entry and conversion figures WPForms records for one form.', 'acrossai-abilities-manager' ), ), array( 'slug' => 'wpforms/create-form', 'label' => __( 'Create Form', 'acrossai-abilities-manager' ), 'description' => __( 'Create a new WPForms form. Requires the WPForms write switch to be on.', 'acrossai-abilities-manager' ), ), array( 'slug' => 'wpforms/update-form-settings', 'label' => __( 'Update Form Settings', 'acrossai-abilities-manager' ), 'description' => __( 'Change one form\'s settings, such as its title or confirmation behaviour. Requires the WPForms write switch to be on.', 'acrossai-abilities-manager' ), ), array( 'slug' => 'wpforms/add-field', 'label' => __( 'Add Field', 'acrossai-abilities-manager' ), 'description' => __( 'Add a field to an existing form. Requires the WPForms write switch to be on.', 'acrossai-abilities-manager' ), ), array( 'slug' => 'wpforms/update-field', 'label' => __( 'Update Field', 'acrossai-abilities-manager' ), 'description' => __( 'Change a field on an existing form. Requires the WPForms write switch to be on.', 'acrossai-abilities-manager' ), ), ); } }