getInputs(); $labels = $this->channel_vocabulary_labels(); $name = (string) ( $inputs['name'] ?? __( '(unnamed)', 'aisentic' ) ); $trigger = $this->resolved_label( $labels['triggers'], (string) ( $inputs['trigger'] ?? '' ) ); $integral = $this->humanize_slug( (string) ( $inputs['integration'] ?? '?' ) ); $calc = $this->resolved_label( $labels['strategies'], (string) ( $inputs['calc_type'] ?? '' ) ); // The strategy label alone ("Fixed amount") never shows the approver how // many points the rule actually awards; append the parameters so a rule // with missing or malformed calc_params is visible before it is approved. $params = $this->normalize_param_map( $inputs['calc_params'] ?? null ); if ( is_array( $params ) ) { $calc .= ' (' . $this->format_calc_params( $params ) . ')'; } $frequency = (string) ( $inputs['frequency'] ?? __( 'unlimited', 'aisentic' ) ); $expiry = array_key_exists( 'expiry_days', $inputs ) && null !== $inputs['expiry_days'] ? sprintf( /* translators: %d: number of days. */ __( '%d days', 'aisentic' ), (int) $inputs['expiry_days'] ) : __( 'never', 'aisentic' ); return sprintf( /* translators: 1: channel name, 2: trigger, 3: integration, 4: calculation, 5: frequency, 6: expiry. */ __( 'Create channel "%1$s": when %2$s (via %3$s), award %4$s, frequency %5$s, points expire %6$s.', 'aisentic' ), $name, $trigger, $integral, $calc, $frequency, $expiry ); } /** * Approval card inputs, with live identifiers resolved to human labels. * * @return array */ public function get_approval_inputs(): array { $raw = $this->getInputs(); $labels = $this->channel_vocabulary_labels(); // Rebuilt (rather than mutated in place) so raw keys like calc_type / // calc_params never leak into the card under an abbreviated header, and // every card shows the actual effective frequency/expiry, including the // tool's own defaults for fields the model left unset. $inputs = []; if ( isset( $raw['name'] ) ) { $inputs['name'] = $raw['name']; } if ( isset( $raw['trigger'] ) ) { $inputs['trigger'] = $this->resolved_label( $labels['triggers'], (string) $raw['trigger'] ); } if ( isset( $raw['integration'] ) ) { $inputs['integration'] = $this->humanize_slug( (string) $raw['integration'] ); } if ( isset( $raw['calc_type'] ) ) { $inputs['calculation_type'] = $this->resolved_label( $labels['strategies'], (string) $raw['calc_type'] ); } $calc_params = $this->normalize_param_map( $raw['calc_params'] ?? null ); if ( is_array( $calc_params ) ) { $inputs['calculation_details'] = $this->format_calc_params( $calc_params ); } $inputs['frequency'] = isset( $raw['frequency'] ) ? (string) $raw['frequency'] : __( 'Unlimited', 'aisentic' ); $inputs['points_expire'] = isset( $raw['expiry_days'] ) ? sprintf( /* translators: %d: number of days. */ __( '%d days', 'aisentic' ), (int) $raw['expiry_days'] ) : __( 'Never', 'aisentic' ); if ( isset( $raw['conditions'] ) ) { $inputs['conditions'] = $raw['conditions']; } return $inputs; } /** * Execute the tool after approval. * * @param string $name Channel name. * @param string $trigger Trigger identifier. * @param string $integration Integration identifier. * @param string $calc_type Calculation strategy identifier. * @param mixed $calc_params Calculation parameters as a JSON object string (or an array, when the model sends one). * @param string|null $frequency Frequency value. * @param int|null $expiry_days Expiry days. * @param mixed $conditions Condition tree as a JSON object string (or an array, when the model sends one). * @return array */ public function __invoke( string $name, string $trigger, string $integration, string $calc_type, $calc_params = null, ?string $frequency = null, ?int $expiry_days = null, $conditions = null ): array { $check = $this->guard( 'manage_pointics', 'manage_options' ); if ( $check ) { return $check; } $ready = $this->pointics_tables_ready(); if ( $ready ) { return $this->steer( $ready ); } if ( '' === trim( $name ) || '' === trim( $trigger ) || '' === trim( $integration ) || '' === trim( $calc_type ) ) { return $this->steer( [ 'error' => __( 'name, trigger, integration, and calc_type are required. Use pointics-list-channel-building-blocks for live values.', 'aisentic' ), 'code' => 'validation', 'status' => 422, ] ); } $calc_params = $this->normalize_param_map( $calc_params ); if ( false === $calc_params ) { return $this->param_map_error( 'calc_params' ); } $conditions = $this->normalize_param_map( $conditions ); if ( false === $conditions ) { return $this->param_map_error( 'conditions' ); } // Pointics stores calc_params verbatim and every strategy falls back to // zero points for a field it cannot read, so an unusable map has to fail // here — otherwise the channel is created and silently awards nothing. $invalid = $this->validate_calc_params( sanitize_text_field( $calc_type ), $calc_params ); if ( $invalid ) { return $invalid; } try { $body = [ 'name' => sanitize_text_field( $name ), 'trigger' => sanitize_text_field( $trigger ), 'integration' => sanitize_text_field( $integration ), 'calc_type' => sanitize_text_field( $calc_type ), ]; if ( null !== $calc_params ) { $body['calc_params'] = $calc_params; } if ( null !== $frequency ) { $body['frequency'] = sanitize_text_field( $frequency ); } if ( null !== $expiry_days ) { $body['expiry_days'] = $expiry_days; } if ( null !== $conditions ) { $body['conditions'] = $conditions; } $request = $this->json_request( 'POST', '/pointics/v1/channels', $body ); $controller = pointics_container()->make( \Pointics\Api\Controllers\Channels_Controller::class ); $duplicate = $this->find_duplicate_by_name( $controller, 'list_channels', 'pointics-list-channels', $name, 'channel' ); if ( is_array( $duplicate ) ) { return $duplicate; } if ( null !== $duplicate ) { return $this->steer( [ /* translators: %1$s channel name, %2$d channel ID. */ 'error' => sprintf( __( 'A channel named "%1$s" already exists (ID: %2$d). Use pointics-update-channel instead of creating a duplicate.', 'aisentic' ), $name, $duplicate ), 'code' => 'logical_failure', 'status' => 409, ] ); } $data = $this->extract_response( $controller->create_channel( $request ), __( 'Failed to create the Pointics channel.', 'aisentic' ), 'pointics-create-channel' ); if ( $this->is_error( $data ) ) { return $data; } $channel = $this->shape_channel( $data ); if ( ! isset( $channel['id'] ) ) { return $this->steer( [ 'error' => __( 'Pointics created the channel without returning its ID. Verify with pointics-list-channels before retrying.', 'aisentic' ), 'code' => 'internal', 'status' => null, ] ); } return [ 'success' => true, 'channel' => $channel, 'message' => sprintf( /* translators: %s: channel name. */ __( 'Pointics channel "%s" created successfully.', 'aisentic' ), $channel['name'] ?? $name ), ]; } catch ( \Throwable $e ) { Log::error( 'Pointics_Create_Channel_Tool error', [ 'error' => $e->getMessage() ] ); return $this->steer( [ 'error' => __( 'Failed to create the Pointics channel.', 'aisentic' ), 'code' => 'internal', 'status' => null, ] ); } } }