'Coupon Code', 'tag' => '{coupon_code}', ]; return $merge_tags; } /** * Replaces the coupon code merge tag with the actual coupon code. * * @param string $text The text in which to replace the merge tag. * @param array $form The current form. * @param array $entry The current entry. * @param bool $url_encode Whether to URL encode the replacement text. * @param bool $esc_html Whether to escape HTML in the replacement text. * * @return string The text with the merge tag replaced. */ public function replace_coupon_code_merge_tag( $text, $form, $entry, $url_encode = false, $esc_html = true ) { // Short-circuit for compatibility with PHP 7.4. if ( false === is_string( $text ) || false === strpos( $text, '{coupon_code}' ) ) { return $text; } $eid = isset( $entry['id'] ) ? (int) $entry['id'] : 0; $coupon_code = 0 !== $eid ? gform_get_meta( $eid, 'created_coupon_code' ) : ''; if ( '' === $coupon_code ) { $coupon_code = 'No coupon code available'; } if ( true === $esc_html ) { $coupon_code = esc_html( $coupon_code ); } if ( true === $url_encode ) { $coupon_code = rawurlencode( $coupon_code ); } return str_replace( '{coupon_code}', $coupon_code, $text ); } /** * Retrieves the settings array containing configuration details. * * @return array An associative array including the settings, such as titles and fields. */ public function get_settings() { $forms = GFAPI::get_forms(); $validate_date = static function ( $field, $date ) { $date = is_string( $date ) ? trim( $date ) : ''; if ( '' === $date ) { return; } // Basic format: MM/DD/YYYY $valid_format = ( 1 === preg_match( '/^(0[1-9]|1[0-2])\/(0[1-9]|[12]\d|3[01])\/\d{4}$/', $date ) ); if ( true !== $valid_format || false === strtotime( $date ) ) { $field->set_error( 'The date must be valid and in MM/DD/YYYY format.' ); } }; $form_choices = array_map( static function ( $form ) { return [ 'label' => $form['title'], 'value' => $form['id'], ]; }, $forms ); return [ 'title' => 'Coupon Code', 'fields' => [ [ 'name' => 'coupon_code_field', 'type' => 'text', 'label' => 'Coupon Code', 'required' => true, 'class' => 'merge-tag-support mt-position-right', 'tooltip' => 'Enter the coupon code. Merge tags can be used to pass in the value of a field.', ], [ 'name' => 'name_field', 'type' => 'text', 'label' => 'Coupon Name', 'tooltip' => 'Enter the coupon name. If not configured, the coupon code will be used as the name.', 'class' => 'merge-tag-support mt-position-right', ], [ 'name' => 'amount', 'type' => 'text', 'label' => 'Coupon Amount', 'tooltip' => 'Enter the amount of the coupon.', 'required' => true, 'class' => 'merge-tag-support mt-position-right', 'validation_callback' => function ( $field, $amount ) { if ( is_numeric( $amount ) && $amount < 1 ) { $field->set_error( 'The amount must be greater than zero.' ); } }, ], [ 'name' => 'type', 'type' => 'radio', 'horizontal' => true, 'label' => 'Discount Type', 'tooltip' => 'Select the type of discount.', 'required' => true, 'choices' => [ [ 'label' => 'Percentage', 'value' => 'percentage', ], [ 'label' => 'Flat', 'value' => 'flat', ], ], ], [ 'name' => 'target_form', 'label' => 'Target Form', 'type' => 'select', 'choices' => $form_choices, 'tooltip' => 'Select the form to use the coupon code in.', ], [ 'name' => 'coupon_start_date', 'type' => 'text', 'label' => 'Coupon Start Date', 'tooltip' => 'Enter the start date of the coupon in MM/DD/YYYY format.', 'validation_callback' => $validate_date, ], [ 'name' => 'coupon_expiration_date', 'type' => 'text', 'label' => 'Coupon Expiration Date', 'tooltip' => 'Enter the expiration date of the coupon in MM/DD/YYYY format.', 'validation_callback' => $validate_date, ], [ 'name' => 'coupon_limit', 'type' => 'text', 'label' => 'Coupon Limit', 'tooltip' => 'Enter the number of times the coupon can be used. Default is unlimited. Merge tags can be used to pass in the value of a field.', 'class' => 'merge-tag-support mt-position-right', ], [ 'name' => 'stackable', 'type' => 'checkbox', 'label' => 'Stackable', 'tooltip' => 'Check this box to allow the coupon to be used with other coupons.', 'choices' => [ [ 'label' => '', 'name' => 'stackable', ], ], ], [ 'name' => 'required_fields[]', 'type' => 'field_select', 'label' => 'Required Fields', 'tooltip' => 'Select fields that must not be empty for the coupon to be created.', 'required' => false, 'multiple' => true, ], ], ]; } /** * Process the step. Create the coupon based on the form submission. * * @return bool Is the step complete? */ public function process() { $form = $this->get_form(); $entry = $this->get_entry(); // Idempotency: if a coupon was already created for this entry, do not create another. $eid = isset( $entry['id'] ) ? (int) $entry['id'] : 0; $existing_cc = 0 !== $eid ? gform_get_meta( $eid, 'created_coupon_code' ) : ''; if ( '' !== $existing_cc ) { $this->add_note( sprintf( 'Coupon already created for this entry: %s', $existing_cc ) ); return true; } if ( true === $this->should_abort( $entry ) ) { $this->add_note( 'Coupon creation aborted: required fields are empty.' ); return true; } $coupon_code = $this->get_coupon_codes( $this->coupon_code_field, $form, $entry ); if ( '' === $coupon_code ) { $this->add_note( 'No coupon codes found.' ); return true; } // Resolve coupon name. if ( false === $this->name_field || '' === $this->name_field ) { $coupon_name = $coupon_code; } else { $coupon_name = $this->name_field; $coupon_name = GFCommon::replace_variables( $coupon_name, $form, $entry ); $coupon_name = '' === $coupon_name ? $coupon_code : $coupon_name; } $amount = GFCommon::replace_variables( $this->amount, $form, $entry ); $type = $this->type; // Ensure target form exists. $target_form_id = isset( $this->target_form ) ? (int) $this->target_form : 0; $target_form = 0 !== $target_form_id ? GFAPI::get_form( $target_form_id ) : null; if ( empty( $target_form ) || ! is_array( $target_form ) ) { $this->add_note( sprintf( 'Coupon not created: target form %d not found.', $target_form_id ) ); return true; } $meta = [ 'form_id' => $target_form_id, 'coupon_name' => $coupon_name, 'coupon_code' => strtoupper( $coupon_code ), 'coupon_type' => $type, 'coupon_amount' => $amount, 'coupon_start' => $this->coupon_start_date, 'coupon_expiration' => $this->coupon_expiration_date, 'coupon_limit' => GFCommon::replace_variables( $this->coupon_limit, $form, $entry ), 'coupon_stackable' => $this->stackable, ]; $final_code = $this->create_coupon( $meta, $target_form_id ); if ( '' === $final_code ) { $this->add_note( 'Coupon creation failed.' ); return true; } // Store the coupon code in entry meta for use with merge tags. gform_update_meta( $eid, 'created_coupon_code', $final_code ); $this->add_note( sprintf( 'Coupon created: %s', $final_code ) ); return true; } /** * Retrieves and processes coupon codes by replacing variables with their corresponding values. * * @param string $coupon_code_field The field containing the coupon code that needs processing. * @param array $form The form data where the coupon code field exists. * @param array $entry The entry data associated with the submitted form. * * @return string The processed coupon code with replaced variables. */ public function get_coupon_codes( $coupon_code_field, $form, $entry ) { return str_replace( ' ', '', GFCommon::replace_variables( $coupon_code_field, $form, $entry ) ); } /** * Check if the coupon creation should be aborted. * * @param array $entry The entry object. * @return bool True if the coupon creation should be aborted. */ public function should_abort( $entry ) { $this->required_fields = array_filter( $this->required_fields ); if ( empty( $this->required_fields ) ) { return false; } foreach ( $this->required_fields as $field_id ) { $value = rgar( $entry, (string) $field_id ); if ( rgblank( $value ) ) { return true; } } return false; } /** * Creates a coupon and associates it with a form in Gravity Forms. * * @param array $meta An array of coupon metadata, including details such as coupon name, code, amount, type, start date, expiration date, and usage limits. * @param int $form_id The ID of the form to associate the coupon with. * * @return string Coupon code used, or empty string on failure. */ public function create_coupon( $meta, $form_id ) { if ( ! class_exists( 'GFCoupons' ) ) { return ''; } // Ensure Coupons config is loaded (compat guard for different versions). if ( is_callable( 'gf_coupons' ) ) { gf_coupons()->get_config( [ 'id' => 0 ], false ); } else { /* @noinspection PhpDynamicAsStaticMethodCallInspection */ GFCoupons::get_config( [ 'id' => 0 ], false ); } // Duplicate detection + auto-increment suffix (bounded attempts) $base_code = isset( $meta['coupon_code'] ) ? (string) $meta['coupon_code'] : ''; $code = $base_code; $attempts = 0; $max = 25; if ( is_callable( 'gf_coupons' ) ) { while ( $attempts < $max ) { $existing = gf_coupons()->get_coupons_by_codes( [ $code ], (int) $form_id ); if ( empty( $existing ) ) { break; } $attempts++; $code = $base_code . '-' . $attempts; } } if ( $attempts >= $max ) { return ''; } if ( is_callable( 'gf_coupons' ) ) { $meta['gravityForm'] = $meta['form_id'] ?: 0; // phpcs:ignore Universal.Operators.DisallowShortTernary.Found $meta['couponName'] = $meta['coupon_name']; $meta['couponCode'] = $code; $meta['couponAmountType'] = $meta['coupon_type']; $meta['couponAmount'] = $meta['coupon_amount']; $meta['startDate'] = $meta['coupon_start']; $meta['endDate'] = $meta['coupon_expiration']; $meta['usageLimit'] = $meta['coupon_limit']; $meta['isStackable'] = $meta['coupon_stackable']; $meta['usageCount'] = 0; unset( $meta['form_id'] ); gf_coupons()->insert_feed( $form_id, true, $meta ); return $code; } /* @noinspection PhpUndefinedClassInspection */ GFCouponsData::update_feed( 0, $form_id, true, $meta ); return $code; } } Gravity_Flow_Steps::register( new BLD_CouponCodeWorkflowStep() ); } );