is_pointics_connected() ) { return $tools; } // Rewards. $tools[] = new Pointics_List_Rewards_Tool(); $tools[] = new Pointics_Get_Reward_Tool(); $tools[] = new Pointics_Create_Reward_Tool(); $tools[] = new Pointics_Update_Reward_Tool(); $tools[] = new Pointics_Delete_Reward_Tool(); $tools[] = new Pointics_Get_Reward_Stats_Tool(); $tools[] = new Pointics_List_Redemption_Methods_Tool(); // Channels. $tools[] = new Pointics_List_Channels_Tool(); $tools[] = new Pointics_Get_Channel_Tool(); $tools[] = new Pointics_Create_Channel_Tool(); $tools[] = new Pointics_Update_Channel_Tool(); $tools[] = new Pointics_Delete_Channel_Tool(); $tools[] = new Pointics_Trigger_Channel_Tool(); $tools[] = new Pointics_List_Channel_Building_Blocks_Tool(); // Members & analytics. $tools[] = new Pointics_Find_Member_Tool(); $tools[] = new Pointics_Get_Analytics_Overview_Tool(); $tools[] = new Pointics_Get_Activity_Tool(); $tools[] = new Pointics_Get_Redemption_Types_Tool(); $tools[] = new Pointics_Get_Top_Customers_Tool(); $tools[] = new Pointics_Get_Top_Rules_Tool(); return $tools; } /** * Tell the agent which Pointics integrations this site actually has. * * The admin-page sitemap is written once per plugin and describes every * integration Pointics can support, so a search for "which order statuses * award points" returns the WooCommerce settings whether or not WooCommerce * is installed. On a site running only Eventin that answer is confidently * wrong, and it points the merchant at a screen that is not there. * * Pointics already knows the answer: the trigger registry filters providers * by is_available(). Passing that list in costs one short line and lets the * agent name the right integration, or say the plugin is missing. * * @param string $prompt The system prompt built so far. * @return string */ public function add_active_integrations( string $prompt ): string { if ( ! $this->is_pointics_connected() || ! function_exists( 'pointics_container' ) ) { return $prompt; } $labels = $this->active_integration_labels(); if ( [] === $labels ) { return $prompt; } $prompt .= "\n## Pointics on this site\n"; $prompt .= 'Integrations installed and active: ' . implode( ', ', $labels ) . ".\n"; $prompt .= "That is the complete list. An admin-page search covers every integration Pointics can support, so it returns settings for ones this site does not have. Before naming a settings screen, check it belongs to an integration above. If the closest match belongs to one that is absent, say so and give the equivalent for an integration that is present, or state that the missing plugin would be needed.\n"; $locked = $this->locked_features(); if ( [] !== $locked ) { $prompt .= 'Pointics Pro is NOT installed, so these are unavailable: ' . implode( ', ', $locked ) . ".\n"; $prompt .= "Free registers no admin route for them, so any link to one lands on a 404 — never offer one. When asked about these, say plainly that the feature needs Pointics Pro, describe what it would do, and point at the \"Free vs Pro\" item at the bottom of the Pointics menu (https://themewinter.com/pointics/#pricing).\n"; } return $prompt; } /** * What this site cannot do because Pointics Pro is absent. * * Read from Pointics' own feature registry rather than guessed. Free * registers these gates false and pro re-registers them true, so a false * one is exactly "pro would unlock this". Only the keys named here are * reported: free leaves `referrals`, `expiration` and `visual_rules` ON, so * treating every false flag as a pro feature would invent restrictions. * * `pro` is not a capability but the answer to "did the add-on boot", so it * short-circuits the whole paragraph instead of appearing inside it. * * Never fatal: a chat turn must not die because the registry moved. * * @return array */ private function locked_features(): array { $pro_only = [ 'tiers_unlimited' => 'creating and deleting loyalty levels', 'white_label' => 'hiding the Pointics attribution on the widget', 'analytics_history' => 'analytics history beyond 30 days', 'advanced_rules' => 'advanced earning rules', ]; try { $features = pointics_container()->make( \Pointics\Support\Features::class ); if ( ! is_object( $features ) || ! method_exists( $features, 'all' ) ) { return []; } $flags = $features->all(); if ( ! empty( $flags['pro'] ) ) { return []; } $locked = []; foreach ( $pro_only as $key => $label ) { if ( array_key_exists( $key, $flags ) && ! $flags[ $key ] ) { $locked[] = $label; } } return $locked; } catch ( \Throwable $e ) { return []; } } /** * Labels of the integrations whose platform plugin is present. * * Never fatal: a chat turn must not die because the registry moved. * * @return array */ private function active_integration_labels(): array { try { $registry = pointics_container()->make( \Pointics\Integrations\Trigger_Registry::class ); if ( ! is_object( $registry ) || ! method_exists( $registry, 'available' ) ) { return []; } $labels = []; foreach ( $registry->available() as $provider ) { if ( is_object( $provider ) && method_exists( $provider, 'label' ) ) { $label = (string) $provider->label(); if ( '' !== $label ) { $labels[] = $label; } } } return array_values( array_unique( $labels ) ); } catch ( \Throwable $e ) { return []; } } /** * Check whether Pointics is active and connected via Aisentic settings. * * Protected rather than private so a test can model a connected site without * declaring pointics(): two other tests in this suite assert that function * stays undefined, and a global declaration cannot be undone. * * @return bool */ protected function is_pointics_connected(): bool { if ( ! function_exists( 'pointics' ) ) { return false; } $saved = get_option( Integration_Controller::OPTION_KEY, [] ); $status = $saved['pointics']['status'] ?? ''; return 'connected' === $status; } }